Your code implementation does not utilize variadic tuple types as described in the documentation. Instead, the 'curry()' function you are creating takes a partial parameter list and may return another function that handles the remaining parameters. This means that the initial tuple of parameters 'T' could be broken down into multiple pieces, which cannot be automatically inferred by context like the 'partialCall()' function from the documentation.
To make it work, you need to explicitly split the 'T' tuples into possible subtuples. Here is how the desired output type of 'curry()' can be represented:
type Curried<A extends any[], R> =
<P extends Partial<A>>(...args: P) => P extends A ? R :
A extends [...SameLength<P>, ...infer S] ? S extends any[] ? Curried<S, R>
: never : never;
type SameLength<T extends any[]> = Extract<{ [K in keyof T]: any }, any[]>
In simpler terms, 'Curried' is a generic function that requires arguments of some tuple type 'P', constrained within 'Partial<A>'.
For tuples, 'Partial<A>' allows leaving out any suffix of the tuple (from somewhere to the end). For example, '[1, 2, 3]' can match 'Partial<[1,2,3,4,5,6,7]>', but '[1, 2, 4]' won't. Despite nuances with 'undefined', where '[1, undefined, 3]' matches 'Partial<[1,2,3,4,5,6,7]>', these cases should be handled accordingly if significant. The essence is to ensure that the arguments provided to 'Curried<A, R>' constitute a prefix of the 'A' tuple.
The returned type for 'Curried<A, R>' depends on the input prefix 'P'. If 'P' represents the whole tuple 'A', then the return type is simply 'R'. Otherwise, when splitting 'A' into 'P' and its suffix 'S', a new curried function of type 'Curried<S, R>' is returned.
The use and implementation process is demonstrated below:
function curry<A extends any[], R>(fn: (...args: A) => R): Curried<A, R> {
return (...args: any[]): any =>
args.length >= fn.length ? fn(...args as any) : curry((fn as any).bind(undefined, ...args));
}
Type assertions were heavily employed within 'curry()' due to the compiler's difficulty in verifying assignability to 'Curried<A, R>'. By leveraging type assertions, the responsibility shifts to ensuring correct implementation rather than compiler validation.
A sample testing scenario illustrates the functionality:
const fn = (a: string, b: number, c: boolean) => (a.length <= b) === c ? "yep" : "nope";
const cFn = curry(fn);
const val1 = cFn("") (1)(true);
console.log(val1); // yep
const val2 = cFn("", 1, true);
console.log(val2); // yep
const val3 = cFn() () () () ("", 1)() () (true); // yep
Upon testing, intentional errors underscore the compiler's error-checking capabilities:
// errors
cFn(1, 1, true); // error!
// ~ <-- must be a string
cFn("", 1, true, false); // error!
// ~~~~~ <-- Expected 0-3 arguments, but got 4
cFn("") (1) (false) (true); // error!
//~~~~~~~~~~~~~~~ <-- Function call invalid
The displayed errors align with expectations and confirm the correctness of the implementation.
Playground link to code