Reputation: 2050
I have these 2 curry functions below. curry2
receives a function with 2 parameters and returns a curried function and curry3
receives a function with 3 parameters and returns the curried function.
type Curry2 = <A, B, Z>(f: (a: A, b: B) => Z)
=> (a: A)
=> (b: B)
=> Z
const curry2: Curry2 = f => a => b => f(a, b)
type Curry3 = <A, B, C, Z>(f: (a: A, b: B, c: C) => Z)
=> (a: A)
=> (b: B)
=> (c: C)
=> Z
const curry3: Curry3 = f => a => b => c => f(a, b, c)
I want to define a one general curry
function in a most possible type-safe way that curry the passed function f
(regardless of number of parameters f
receives).
I try to do this with Function Overloading in typescript with for example maximum number of parameters for f
, but couldn't find a way.
Is this possible to do this in typescript?
Upvotes: 1
Views: 1531
Reputation: 439
type AnyFunc = (...args: any[]) => any
First we create a type that maps over each argument and carries the return type recursivley
// Create a type signature for a curried function:
// e.g. Curry<(a:string,b:number)=>string> -> (a:string)=>(b:string)=>string
type Curry<Fn extends AnyFunc> =
Parameters<Fn> extends [infer FirstArg, ...infer Rest]
? (arg: FirstArg) => Curry<(...args: Rest) => ReturnType<Fn>>
: ReturnType<Fn>
// more examples
type x = Curry<(arg1: number, arg2: number) => string> // (arg: number) => (arg: number) => string
type y = Curry<(arg1: number, arg2: number, args: number[]) => string> // (arg: number) => (arg: number) => (arg: number[]) => string
The next step is a bit wanky (you should alway think twice before you cast a type), but in this case it should be ok for the most use cases
You can create a recursive function that aggregates every argument until the length of the args of the passed function are equal to the length of the aggregated args.
By type casting the function body to any and providing a Returntype. You can force TS to apply the previous defined Curry
type
function curry<T extends AnyFunc, TAgg extends unknown[]>(func: T, agg?: TAgg): Curry<T> {
const aggregatedArgs = agg ?? []
if (func.length === aggregatedArgs.length) return func(...aggregatedArgs)
return ((arg: any) => curry(func, [...aggregatedArgs, arg])) as any
}
const c = curry((a: string, b: string) => console.log(a, b))
c("a")
c("b")("a")
c("b")("a")("d") // invalid
const b = curry((a: string, b: number) => console.log(a, b))
b("b")("a") // invalid
b("b")(1)("d") // invalid
b("b")(1)
Upvotes: 5