@oazmi/kitchensink
Version:
a collection of personal utility functions
258 lines • 14.5 kB
TypeScript
/** utility functions for creating higher order functions.
*
* @module
*/
import { type BindableFunction } from "./binder.js";
import { HybridTree, type SimpleMap, StrongTree } from "./collections.js";
export interface MemorizeCoreControls<V, K> {
fn: (arg: K) => V;
memory: SimpleMap<K, V>;
}
export declare const memorizeCore: <V, K>(fn: (arg: K) => V, weak_ref?: boolean) => MemorizeCoreControls<V, K>;
/** memorize the return value of a single parameter function.
* further calls with memorized arguments will return the value much quicker.
*
* @example
* ```ts
* import { assertEquals as assertEq } from "jsr:@std/assert"
*
* let fn_call_count = 0
* const fn = (arg: string | number) => {
* fn_call_count++
* return `you owe ${arg} to the bank.`
* }
* const memorized_fn = memorize(fn)
*
* assertEq(memorized_fn("a camel") , "you owe a camel to the bank.")
* assertEq(fn_call_count , 1)
* assertEq(memorized_fn("a camel") , "you owe a camel to the bank.")
* assertEq(fn_call_count , 1)
* assertEq(memorized_fn("5") , "you owe 5 to the bank.")
* assertEq(fn_call_count , 2)
* assertEq(memorized_fn(5) , "you owe 5 to the bank.")
* assertEq(fn_call_count , 3)
* assertEq(memorized_fn("your soul"), "you owe your soul to the bank.")
* assertEq(fn_call_count , 4)
* assertEq(memorized_fn("your soul"), "you owe your soul to the bank.")
* assertEq(fn_call_count , 4)
* ```
*/
export declare const memorize: <V, K>(fn: (arg: K) => V) => (typeof fn);
/** similar to {@link memorize}, but halts its memorization after `n`-unique unmemorized calls are made to the function.
*
* @example
* ```ts
* import { assertEquals as assertEq } from "jsr:@std/assert"
*
* let fn_call_count = 0
* const fn = (arg: string | number) => {
* fn_call_count++
* return `you owe ${arg} to the bank.`
* }
* const memorized_fn = memorizeAtmostN(2, fn)
*
* assertEq(memorized_fn("a camel") , "you owe a camel to the bank.")
* assertEq(fn_call_count , 1)
* assertEq(memorized_fn("a camel") , "you owe a camel to the bank.")
* assertEq(fn_call_count , 1)
* assertEq(memorized_fn("5") , "you owe 5 to the bank.")
* assertEq(fn_call_count , 2)
* // from here on, memorization will be halted
* assertEq(memorized_fn("5") , "you owe 5 to the bank.")
* assertEq(fn_call_count , 2)
* assertEq(memorized_fn(5) , "you owe 5 to the bank.")
* assertEq(fn_call_count , 3)
* assertEq(memorized_fn("your soul"), "you owe your soul to the bank.")
* assertEq(fn_call_count , 4)
* assertEq(memorized_fn("your soul"), "you owe your soul to the bank.")
* assertEq(fn_call_count , 5)
* assertEq(memorized_fn("a camel") , "you owe a camel to the bank.")
* assertEq(fn_call_count , 5)
* ```
*/
export declare const memorizeAtmostN: <V, K>(n: number, fn: (arg: K) => V) => typeof fn;
/** memorizes a function's return value up-until `n`-calls,
* and after this, unmemorized call arguments will either return the optional `default_value` (if it was provided),
* or it will return value of the `n`th call (final call that got memorized).
*
* @example
* ```ts
* import { assertEquals as assertEq } from "jsr:@std/assert"
*
* let fn_call_count = 0
* const fn = (arg: string | number) => {
* fn_call_count++
* return `you owe ${arg} to the bank.`
* }
* const memorized_fn = memorizeAfterN(2, fn, "DEFAULT VALUE!")
*
* assertEq(memorized_fn("a camel") , "you owe a camel to the bank.")
* assertEq(fn_call_count , 1)
* assertEq(memorized_fn("a camel") , "you owe a camel to the bank.")
* assertEq(fn_call_count , 1)
* assertEq(memorized_fn("5") , "you owe 5 to the bank.")
* assertEq(fn_call_count , 2)
* // from here on, memorization will be halted and only `"DEFAULT VALUE!"` will be returned for unmemorized args
* assertEq(memorized_fn("5") , "you owe 5 to the bank.")
* assertEq(fn_call_count , 2)
* assertEq(memorized_fn(5) , "DEFAULT VALUE!")
* assertEq(fn_call_count , 2)
* assertEq(memorized_fn("your soul"), "DEFAULT VALUE!")
* assertEq(fn_call_count , 2)
* assertEq(memorized_fn("your soul"), "DEFAULT VALUE!")
* assertEq(fn_call_count , 2)
* assertEq(memorized_fn("a camel") , "you owe a camel to the bank.")
* assertEq(fn_call_count , 2)
* ```
*/
export declare const memorizeAfterN: <K, V>(n: number, fn: (arg: K) => V, default_value?: V) => typeof fn;
/** memorize function and limit the caching memory used for it, through the use of LRU-scheme.
*
* [LRU](https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU) stands for _least recently used_,
* and the LRU-memorization implemented here memorizes atmost `max_capacity` number of unique arguments,
* before dropping its capacity down to `min_capacity` by discarding the oldest memorized results.
*
* since we are not actually tracking the number of calls made for each argument, this is not a real "LRU-cache",
* since a real one would not discard the oldest memorized argument if it is being called consistiently.
* but that's exactly what we do here: we remove the oldest one irrespective of how often/how-recently it has been used.
*
* TODO: for a more true LRU caching system, we will need to to reference count each memorized argument (I think),
* and that can be achieved via my `RcList` (reference counted list) from {@link "collections"} module.
* but we will first have to create a hybrid of `LimitedStack` and `RcList` before swapping it here with `LimitedStack`.
*
* TODO: put on some darn examples! but that's so much of a hassle. someone please kill me.
* > Hi! It's Ronald McBigDonalds here! What can I do to satiate your hunger today? <br>
* > Get me a BiGGuLP of Seppuku, with a CEO shooter on the side. Ariga-Thanks! <br>
* > Wakarimashta! One juicy order of **bigg** last meal is on it way! Please enjoy your once-in-a-lifetime ketchup splatter moment.
*/
export declare const memorizeLRU: <K, V>(min_capacity: number, max_capacity: number, fn: (arg: K) => V) => typeof fn;
/** memorize the result of a function only once.
* after that, further calls to the function will not invoke `fn` anymore,
* and instead simply return the same memorized value all the time.
*/
export declare const memorizeOnce: <K, V>(fn: (arg: K) => V) => (typeof fn);
export interface memorizeMultiCore_Signature {
<V, ARGS extends any[]>(fn: (...args: ARGS) => V, weak_ref?: false): {
fn: (typeof fn);
memory: typeof StrongTree<ARGS[number], V>;
};
<V, ARGS extends any[]>(fn: (...args: ARGS) => V, weak_ref: true): {
fn: (typeof fn);
memory: typeof HybridTree<ARGS[number], V>;
};
}
export declare const memorizeMultiCore: memorizeMultiCore_Signature;
/** memorize the results of a multi-parameter function.
*
* since references to object type arguments are held strongly in the memorized function's cache, you will probably
* want to manage clearing entries manually, using either {@link Map} methods, or {@link StrongTree} methods.
*/
export declare const memorizeMulti: <V, ARGS extends any[]>(fn: (...args: ARGS) => V) => (typeof fn);
/** memorize the results of a multi-parameter function, using a weak cache.
*
* the used arguments are cached _weakly_, meaning that if a non-primitive object `obj` was used as an argument,
* then `obj` is **not** strongly bound to the memorized function's cache, meaning that if `obj` becomes inaccessible in all scopes,
* then `obj` will become garbage collectible, which will then also clear the cache's reference to `obj` (and its memorized result).
*/
export declare const memorizeMultiWeak: <V, ARGS extends any[]>(fn: (...args: ARGS) => V) => (typeof fn);
/** this is the return type of {@link curry}, made for the sole purpose of type recursion. */
export type CurrySignature<FN extends (...args: any) => any, R extends ReturnType<FN> = ReturnType<FN>, ARGS extends Parameters<FN> = Parameters<FN>> = ARGS extends [infer ARG0, ...infer REST] ? (arg: ARG0) => CurrySignature<(...rest_args: REST) => R> : R;
/** this is the return type of {@link CurryMultiSignature}, made for the sole purpose of type recursion. */
export type CurryMultiSignature<FN extends BindableFunction<THIS, any, any, any>, R extends (FN extends BindableFunction<THIS, any, any, infer P> ? P : void) = ReturnType<FN>, THIS extends any = any> = <A extends (FN extends BindableFunction<THIS, infer P, any, R> ? P : never), B extends (FN extends BindableFunction<THIS, A, infer P, R> ? P : never), FN_B extends (B extends never[] ? never : (...args_b: B) => R)>(...args_a: A) => B extends never[] ? R : CurryMultiSignature<FN_B, ReturnType<FN_B> & R, THIS>;
/** curry a function `fn`, with optional `thisArg` option for binding as the `this`-object.
*
* what is currying? it allows a multi-parameter function to be transformed into a higher order function that always takes one argument,
* and spits out another function which also take only one argument, and so on... until all parameters have been filled,
* upon which the final function finally evaluates into the return value (type parameter {@link R} in this case).
*
* note that this function relies on `fn.length` property to identify the number of **required** arguments taken by `fn`.
* this means that default valued arguments (such as `c` in `fn: (a: number, b: number, c = 5) => number`), or rest/spread
* arguments (such as `args` in `fn: (a: number, b: number, ...args: number[]) => number`), are not considered as required,
* and thus do not increment the count of `fn.length`.
*
* currying is usually poorly implemented through the use of closure.
* for instance:
*
* ```ts ignore
* const curried_fn = ((arg0) => (arg1) => (arg2) => fn(arg1, arg2, arg3))
* const output = curried_fn("arg0")("arg1")("arg2")
* ```
*
* this is bad because when you evaluate a curry with N-parameters, you also have to make N-calls (albeit it being tail-calls),
* instead of just one call, should you have had all the parameters from the beginning.
* not to mention that all javascript engines famously do not perform tail-call optimizations.
*
* so here, I've implemented currying using the `bind` method, which means that once all parameters are filled, the function goes through only one call (no overheads).
* the same example from before would translate into the following when binding is used:
*
* ```ts ignore
* const thisArg = undefined
* const curried_fn = fn.bind(thisArg, arg0).bind(thisArg, arg1).bind(thisArg, arg2)
* const output = curried_fn("arg0")("arg1")("arg2")
* ```
*
* do note that it is very possible for the javascript engine to internally create tail-recursion closure for every argument binding,
* resulting in the same unoptimized function that we discredited just a while ago.
* without benchmarking, I cannot say for sure which implementation is more performant.
*
* @param fn the function to curry
* @param thisArg provide an optional argument to use as the `this` object inside of `fn`
* @returns a series of single argument partial functions that does not evaluate until all parameters have been provided
*
* @example
* ```ts
* import { assertEquals as assertEq } from "jsr:@std/assert"
*
* const abcd = (a: number, b: string, c: boolean, d: symbol): string => (String(a) + b + String(c) + " " + String(d))
* const abcd_curry = curry(abcd)
*
* abcd_curry satisfies ((arg: number) => (arg: string) => (arg: boolean) => (arg: symbol) => string)
*
* assertEq(
* ((((abcd_curry(42) satisfies ((arg: string) => (arg: boolean) => (arg: symbol) => string)
* )(" hello to za warudo! ") satisfies ((arg: boolean) => (arg: symbol) => string)
* )(true) satisfies ((arg: symbol) => string)
* )(Symbol.iterator) satisfies (string)
* ),
* "42 hello to za warudo! true Symbol(Symbol.iterator)",
* )
* ```
*/
export declare const curry: <FN extends (...args: any) => any, R extends ReturnType<FN> = ReturnType<FN>, ARGS extends Parameters<FN> = Parameters<FN>>(fn: FN, thisArg?: ThisParameterType<FN>) => CurrySignature<FN, R, ARGS>;
/** come here, come all! greet the **Types' Olympic Champion** of winter 2024.
*
* it took a while to correctly apply a multitude of gymnastics to get it functioning, but the dedication has paid off!
* please give `curryMulti` a round of applause!
* and don't forget that currying a diverse variety of types all at once brings strength!
* > (said a nation right before its downfall)
*
* now that introductions are over: {@link curryMulti} behaves very much like {@link curry},
* the only difference being that you can bind an arbitrary number of arguments to the curried `fn` function,
* instead of just a single argument at a time (like in the case of {@link curry}).
*
* @param fn the function to multi-curry
* @param thisArg provide an optional argument to use as the `this` object inside of `fn`
* @param remaining_args number of arguments remaining until all parameters (required kind, ideally) are filled. intended for internal use onkly
* @returns a curried function that consumes variable number of arguments, until all required parameters are available, after which a return value is spat out
*
* @example
* ```ts
* import { assertEquals as assertEq } from "jsr:@std/assert"
*
* const abcd = (a: number, b: string, c: boolean, d: symbol): string => (String(a) + b + String(c) + " " + String(d))
* const abcd_diversity_curry = curryMulti(abcd)
*
* abcd_diversity_curry satisfies CurryMultiSignature<(a: number, b: string, c: boolean, d: symbol) => string, string, any>
*
* assertEq(
* (((abcd_diversity_curry(
* 42, " hello to za warudo! ") satisfies CurryMultiSignature<(c: boolean, d: symbol) => string, string, any>
* )(true) satisfies CurryMultiSignature<(d: symbol) => string, string, any>
* )(Symbol.iterator) satisfies (string)
* ),
* "42 hello to za warudo! true Symbol(Symbol.iterator)",
* )
* ```
*/
export declare const curryMulti: <FN extends BindableFunction<THIS, any, any, any>, R extends (FN extends BindableFunction<THIS, any, any, infer P> ? P : void) = ReturnType<FN>, THIS extends unknown = any>(fn: FN, thisArg?: THIS, remaining_args?: number) => CurryMultiSignature<FN, R, THIS>;
//# sourceMappingURL=lambda.d.ts.map