Last active
November 16, 2018 00:24
-
-
Save lumie1337/aabf0d99645d5f3e8945d3ee0446ad20 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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