utility functions for creating other general purpose functions that can bind their passed function's functionality to some specific object.
those are certainly a lot of words thrown in the air with no clarity as to what am I even saying.
just as they say, a code block example is worth a thousand assembly instructions. here's the gist of it:

import { assertEquals } from "jsr:@std/assert"

const bind_pushing_to = bindMethodFactory(Array.prototype.push) // equivalent to `bindMethodFactoryByName(Array.prototype, "push")`
const bind_seek_to = bindMethodFactory(Array.prototype.at, -1) // equivalent to `bindMethodFactoryByName(Array.prototype, "at", -1)`
const bind_splicing_to = bindMethodFactoryByName(Array.prototype, "splice") // equivalent to `bindMethodFactory(Array.prototype.splice)`
const bind_clear_to = bindMethodFactoryByName(Array.prototype, "splice", 0) // equivalent to `bindMethodFactory(Array.prototype.splice, 0)`

const my_array = [1, 2, 3, 4, 5, 6]
const push_my_array = bind_pushing_to(my_array)
const seek_my_array = bind_seek_to(my_array)
const splice_my_array = bind_splicing_to(my_array)
const clear_my_array = bind_clear_to(my_array) as (deleteCount?: number, ...items: number[]) => number[]

push_my_array(7, 8, 9)
assertEquals(my_array, [1, 2, 3, 4, 5, 6, 7, 8, 9])
assertEquals(seek_my_array(), 9)
splice_my_array(4, 3)
assertEquals(my_array, [1, 2, 3, 4, 8, 9])
clear_my_array()
assertEquals(my_array, [])

you may have some amateur level questions about why? would anyone want to do that. here is why:

  • calling a method via property access is slower. so when you call an array arr's push or pop methods a million times, having a bound function for that specific purpose will be quicker by about x1.3 times:
import { assertLess } from "jsr:@std/assert"
import { timeIt } from "./timeman.ts"

// WARNING: JIT is pretty smart, and the test consistiently fails for `i` and `j` > `10_000_000`,
// this is despite my efforts to make it difficult for the JIT to optimize the slow method, by computing some modulo and only popping when it is zero.
// thus to be safe, I tuned `i` and `j` down to `100_000` iterations

let i = 100_000, j = 100_000, sum1 = 0, sum2 = 0
const
arr1 = Array(777).fill(0).map(Math.random),
arr2 = Array(777).fill(0).map(Math.random)

// slower way:
const t1 = timeIt(() => {
while(i--) {
const new_length = arr1.push(Math.random())
sum1 += ((new_length + i) % 3 === 0 ? arr1.pop()! : arr1.at(-1)!)
}
})

// faster way (allegedly):
const
push_arr2 = Array.prototype.push.bind(arr2),
pop_arr2 = Array.prototype.pop.bind(arr2),
seek_arr2 = Array.prototype.at.bind(arr2, -1)
const t2 = timeIt(() => {
while(j--) {
const new_length = push_arr2(Math.random())
sum2 += ((new_length + i) % 3 === 0 ? pop_arr2()! : seek_arr2()!)
}
})

// assertLess(t2, t1) // TODO: RIP, performance gains have diminished in deno. curse you V8 JIT.
// I still do think that in non-micro-benchmarks and real life applications (where there are a variety of objects and structures),
// there still is a bit of performance gain. and lets not forget the benefit of minifiability.

next, you may be wondering why not destructure the method or assign it to a variable?

  • this cannot be generally done for prototype-bound methods, because it needs the context of who is the caller (and therefor the this of interest). all builtin javascript class methods are prototype-bound. meaning that for every instance of a builtin class no new functions are specifically created for that instance, and instead, the instance holds a reference to the class's prototype object's method, but applies itself as the this when called.
import { assertEquals, assertThrows } from "jsr:@std/assert"

const arr = [1, 2, 3, 4, 5, 6]

// prototype-bound methods need to be called via property access, otherwise they will loose their `this` context when uncoupled from their parent object
const { push, pop } = arr
assertThrows(() => push(7, 8, 9)) // `TypeError: Cannot convert undefined or null to object`
assertThrows(() => pop()) // `TypeError: Cannot convert undefined or null to object`

const
push2 = arr.push,
pop2 = arr.pop
assertThrows(() => push2(7, 8, 9)) // `TypeError: Cannot convert undefined or null to object`
assertThrows(() => pop2()) // `TypeError: Cannot convert undefined or null to object`

// but you can do the binding yourself too to make it work
const push3 = arr.push.bind(arr) // equivalent to `Array.prototype.push.bind(arr)`
const pop3 = arr.pop.bind(arr) // equivalent to `Array.prototype.pop.bind(arr)`
push3(7, 8, 9) // will work
pop3() // will work

// or use this submodule to do the same thing:
// import { bind_array_pop, bind_array_push } from "@oazmi/kitchensink/binder"
const push4 = bind_array_push(arr)
const pop4 = bind_array_pop(arr)
push4(7, 8, 9) // will work
pop4() // will work
  • finally, property accesses are not easily minifiable (although they do get compressed when gzipped). however, if you bind your method calls to a variable, then it will become minifiable, which is somewhat the primary motivation for this submodule.

with full automatic typing, you won't be compensating in any way.
on the side note, it was figuring out the automatic typing that took me almost 16 hours just to write 3 lines of equivalent javascript code for the main 2 factory functions of this submodule.
curse you typescript!

Index

Type Aliases

Functions