Skip to content

Instantly share code, notes, and snippets.

@webstrand
Last active June 3, 2021 20:12
Show Gist options
  • Select an option

  • Save webstrand/0408309b35ef1d585c1f79bf79e0caab to your computer and use it in GitHub Desktop.

Select an option

Save webstrand/0408309b35ef1d585c1f79bf79e0caab to your computer and use it in GitHub Desktop.
Example of function currying
// See also https://gist.github.com/webstrand/bac2bc752e3f6b22892a155ed1efa536 for definition of Prefixes<T>
/**
* Get the union of all prefixes of some tuple. Finite, optional, and variadic tuples are
* all supported. Labels are only preserved for finite tuples without optional members.
*/
type Prefixes<T extends readonly unknown[]> =
T extends { length: infer X } & { length: infer Y }
? (X extends unknown ? Y extends X ? 0 : 1 : never) extends 0
? number extends T["length"] // If the tuple is variadic
? PrefixesNoLabels<T> // we bail out and use the variadic supporting type
: PrefixesFinite<T>
: PrefixesNoLabels<T> // otherwise we bail out and use the optional member supporting type
: never;
/** @private
* Get the union of all prefixes of some tuple. Only finite tuples are supported.
*/
type PrefixesFinite<T extends readonly unknown[]> =
T extends readonly [...infer U, any] // Remove one element at a time
? | [...T] // wrapped to erase any readonly attributes
| PrefixesFinite<U>
: never;
/** @private
* Get the union of all prefixes of some tuple. Finite and variadic tuples are
* both supported. But labels are never preserved.
*/
type PrefixesNoLabels<T extends readonly unknown[], Acc extends readonly unknown[] = [T extends [infer U, ...any] ? U : never]> =
T extends readonly [...Acc, infer U, ...any]
? Acc | PrefixesNoLabels<T, [...Acc, U]>
: T;
type UnionToIntersection<U> =
(U extends U ? (x: U) => 0 : never) extends ((x: infer I) => 0) ? I : never
type Curried<P extends readonly unknown[], R> = P extends readonly [] ? (...args: P) => R : Curried_<P, R>;
type Curried_<P extends readonly unknown[], R> =
P extends readonly []
? R
: Curried__<P, R, Prefixes<P>>;
type Curried__<P extends readonly unknown[], R, V extends readonly unknown[]> =
UnionToIntersection<
V extends V
? (...args: V) => Curried_<P extends readonly [...V, ...infer Q] ? Q : never, R>
: never
>;
type NonUnion<T, Ret = T, U extends T = T> =
(T extends unknown ? U extends T ? 0 : 1 : never) extends 0 ? Ret : never;
type CountRequired<T extends readonly unknown[], Front extends readonly unknown[] = []> =
[] extends T // optional remaining
? Front['length']
: T extends readonly [infer Head, ...infer Tail]
? CountRequired<Tail, [...Front, Head]>
: never;
// This overload is complex because it's trying to confirm that the passed function doesn't
// have any optional arguments. If the function _does_ have optional arguments we force the
// user to pass in an explicit number of non-optional arguments at runtime. We have to do
// this due to the fact that javascript counts optional arguments in fn.length and typescript
// does not. To avoid hairy runtime errors where the user expects to have called a function
// according to the types, we make sure runtime knows how many arguments to expect.
function curry<P extends readonly unknown[], R>(fn: [P] extends [never] ? ((...args: P) => R) : NonUnion<P["length"], ((...args: P) => R)>): 0 extends (1 & P) ? never : Curried<P, R>;
function curry<P extends readonly unknown[], R>(fn: (...args: P) => R, len: CountRequired<P>): 0 extends (1 & P) ? never : Curried<P, R>;
function curry<R>(fn: (...args: any) => R, len: number = fn.length) {
const stash: any[] = [];
const curry = (...args: any): ((...args: any) => any) | R => {
if(stash.length + args.length >= len) {
return fn(...stash, ...args);
}
stash.push(...args);
return curry;
};
return curry;
}
function foo(a: number, b: string, c: boolean, d: null, e: undefined, f?: boolean): void { console.log("foo") }
const cfoo = curry(foo, 5);
const g = cfoo(0)("")(true)(null)(undefined, false);
function bar(a: number, b: string, c: boolean, d: null, e: undefined, f: boolean, ...args: string[]): void { console.log("bar") }
const cbar = curry(bar);
cbar(0)("")(true)(null)(undefined)(false);
function baz(): void { console.log("baz") }
const cbaz = curry(baz);
cbaz();
function qux(x?: string): void { console.log("baz") }
const cqux = curry(qux, 0);
cqux();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment