Type a currying function that turns a multi-argument function into a chain of single-argument calls. Recursive tuple inference peels one parameter per step.
The recursion in this one is the easy part; the constraint on the generic is where the tests bite.
Currying converts a function that takes multiple arguments into a sequence of functions that each take a single argument. Your Currying type must make the curried result accept exactly one argument at a time and yield the original result once all arguments are assigned. The passed-in function may have any number of parameters, and the type has to peel them off one by one without losing a parameter type or the return type. (TypeScript 4.0 or later is recommended, since the solution leans on variadic tuple types.)
const add = (a: number, b: number) => a + b
const three = add(1, 2)
const curriedAdd = Currying(add)
const five = curriedAdd(2)(3)TypeScript 4.0 is recommended in this challenge
Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument.
For example:
const add = (a: number, b: number) => a + b
const three = add(1, 2)
const curriedAdd = Currying(add)
const five = curriedAdd(2)(3)The function passed to Currying may have multiple arguments, you need to correctly type it.
In this challenge, the curried function only accept one argument at a time. Once all the argument is assigned, it should return its result.
View on GitHub: https://tsch.js.org/17
Change the following code to make the test cases pass (no type check errors).
The finished solution:
type Curried<F> = F extends (...args: infer Args) => infer Return
? Args extends [infer First, ...infer Rest]
? Rest extends []
? (arg: First) => Return
: (arg: First) => Curried<(...args: Rest) => Return>
: () => Return
: never
declare function Currying<F extends Function>(fn: F): Curried<F>The core idea: capture the parameter list as a tuple, then peel one parameter off the front per step, wrapping what remains in a new function type each time.
F extends (...args: infer Args) => infer Return extracts two things from any function type: Args, the parameter list as a tuple type, and Return. For (a: string, b: number, c: boolean) => true you get:
// Args = [a: string, b: number, c: boolean]
// Return = trueGetting parameters as a tuple is what makes the rest possible. Tuples can be destructured with variadic patterns; function signatures themselves cannot.
Args extends [infer First, ...infer Rest] is the tuple analogue of a head/tail split in functional programming. First grabs the first element's type; the spread ...infer Rest captures everything after it as a new tuple. Then the recursion kicks in:
[object Object]We emit one single-argument function and delegate the remaining parameters by rebuilding a smaller function type (...args: Rest) => Return, then running Curried on it again. Tracing curried1 from the tests:
Curried<(a: string, b: number, c: boolean) => true>
// = (arg: string) => Curried<(b: number, c: boolean) => true>
// = (arg: string) => (arg: number) => Curried<(c: boolean) => true>
// = (arg: string) => (arg: number) => (arg: boolean) => trueEach recursion step consumes exactly one parameter, so a seven-parameter function (like curried2 in the tests) unrolls into a chain of seven single-argument functions. No manual overloads required.
The recursion bottoms out in two different ways, and both matter:
Rest extends []: the current parameter is the last one. We return (arg: First) => Return directly instead of recursing again. Without this check, the last step would produce (arg: First) => Curried<() => Return>, i.e. (c: boolean) => () => true, an extra empty call the tests reject.Args doesn't match [infer First, ...infer Rest] at all: the original function had zero parameters. The tests demand Currying(() => true) stays () => true (not collapsing to just true), so the fallback is () => Return.F extends FunctionYou might instinctively constrain the generic as F extends (...args: any[]) => any and then watch the tests fail. The reason is contextual typing: with a call-signature-shaped constraint, the lambda you pass in gets contextually typed against (...args: any[]) => any, and its literal return type true widens to boolean during inference. The test Equal<typeof curried3, () => true> then fails, because () => boolean is not exactly () => true.
Constraining with Function instead (or leaving F unconstrained) gives TypeScript no call signature to contextually type against, so the lambda's own inferred type, literal true and all, flows into F untouched. It is a rare case where the looser constraint is the correct one.
Currying(() => true) must remain () => true, handled by the outer base case.true (the literal), which survives thanks to the Function constraint discussed above.Equal compares types structurally and ignores parameter names, so your emitted (arg: First) => ... matches the expected (a: string) => ... even though the names differ.Infer the parameter tuple, then split and recurse on a rebuilt function type: that pattern is the backbone of most type-level list processing in TypeScript.
This challenge is originally from here.