Skip to content

Instantly share code, notes, and snippets.

@lumie1337
Last active November 16, 2018 00:24
Show Gist options
  • Save lumie1337/aabf0d99645d5f3e8945d3ee0446ad20 to your computer and use it in GitHub Desktop.
Save lumie1337/aabf0d99645d5f3e8945d3ee0446ad20 to your computer and use it in GitHub Desktop.
// First Some Helpers
// This is used as workaround for recursive types
// e.g. type F<T> = { [indirect]: T extends string ? F<T> : never }[indirect] is valid
declare const indirect: unique symbol;
// We use this to do some basic counting on the type level; if you need functions of arity 10+, you need to extend this
type Succ = {
0: 1,
1: 2,
2: 3,
3: 4,
4: 5,
5: 6,
6: 7,
7: 8,
8: 9,
9: 10
10: 11
11: 12
12: never
}
// XXX: Woraround: for some reason typescript infers recursively defined function types as any otherwise, so we use these aliases
type FunctionN<A extends any[], R> = (...args: A) => R
type Function0<R> = () => R
type Function1<A0, R> = (a0: A0) => R
// Now a few Helpers
// creates a singleton type interval between L and U inclusive, e.g. RangeT<1, 4> gives 1 | 2 | 3 | 4
type RangeT<L extends keyof Succ, U extends keyof Succ> = (
L extends Succ[U] ? never : { [indirect]: RangeT<Succ[L], U> | L }
)[typeof indirect]
// Extracts argument types of a function (which will be a tuple type)
type ArgumentsType<T> =
T extends (...args: infer A) => any ? A : never
// Gets the length of a tuple type
type GetLength<T extends any[]> = T extends any[] & {length: infer N} ? N : never
// This is our implementation of the curried function types
type ToCurryType<T> = ToCurryType_Go<ArgumentsType<T>, ReturnTypeT<T>, 0>
type ToCurryType_Go<A extends any[], R extends any, N extends keyof Succ> = (
GetLength<A> extends 0 ? { [indirect]: Function0<R> } : // special case 0-arity function
Succ[N] extends GetLength<A> ? { [indirect]: Function1<A[N], R> } : // base case
{ [indirect]: Function1<A[N], ToCurryType_Go<A, R, Succ[N]>> } // inductive case
)[typeof indirect]
const MaxSupportedGenericArity = 10
const MaxSupportedSpecialisedArity = 4
// the generic currying implementation; it is currently limited to functions of arity 0..10
function curryG<N extends RangeT<0, typeof MaxSupportedGenericArity>, T extends (...args: any[] & {length: N}) => any>(f: T): ToCurryType<T> {
const nargs = (f as any).length;
const vargs: any[] = [];
const curried = (...args: any[]) => vargs.push(...args) >= nargs ? (f as any)(...vargs.slice(0, nargs)) : curried
return curried as any;
}
// here are a few specialised curry implementations which are faster (can be used directly)
function curry2<N extends 2, T extends (...args: any[] & {length: N}) => any>(f: T): ToCurryType<T> {
type A = ArgumentsType<T>
return (a0: A[0]) => (a1: A[1]) => (f as any)(a0, a1);
}
function curry3<N extends 3, T extends (...args: any[] & {length: N}) => any>(f: T): ToCurryType<T> {
type A = ArgumentsType<T>
return (a0: A[0]) => (a1: A[1]) => (a2: A[2]) => (f as any)(a0, a1, a2);
}
function curry4<N extends 4, T extends (...args: any[] & {length: N}) => any>(f: T): ToCurryType<T> {
type A = ArgumentsType<T>
return (a0: A[0]) => (a1: A[1]) => (a2: A[2]) => (a3: A[3]) => (f as any)(a0, a1, a2, a3);
}
// this one picks a specialised implementation for you
function curryC<N extends RangeT<0, typeof MaxSupportedSpecialisedArity>, T extends (...args: any[] & {length: N}) => any>(f: T): ToCurryType<T> {
type A = ArgumentsType<T>
const nargs = (f as any).length;
switch (nargs) {
case 0: return (f as any);
case 1: return (f as any);
// Note: These are necessary because typescript does not narrow the type of F depend on N here
case 2: return curry2(f as any) as any;
case 3: return curry3(f as any) as any;
case 4: return curry4(f as any) as any;
}
}
// a curry wrapper that uses the special case implementation when available
function curry<N extends RangeT<0, typeof MaxSupportedGenericArity>, T extends (...args: any[] & {length: N}) => any>(f: T): ToCurryType<T> {
const nargs = (f as any).length;
if (nargs <= MaxSupportedSpecialisedArity)
return curryC(f as any) as any;
else
return curryG(f);
}
import { curry } from './curry.ts'
// const example: (a: string) => (b: number) => (c: string) => string
const example = curry((a: string, b: number, c: string) => a);
console.log(example("s")(14)("b"))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment