Challenge 3: Extend Curried for Multi-Arg Currying — Possible Solution ==================================================================== // The key change from the chapter's Curried: instead of // always peeling off exactly ONE argument per call, this version accepts // any non-empty prefix of Args per call, and recurses on whatever's left. type CurriedMulti = Args extends [] ? Return : & unknown[]>( ...args: Prefix ) => Prefix extends Args ? Return : CurriedMulti, Return>; // Helper: remove the first N elements of Args, where N = Prefix's length. type DropPrefix = Prefix extends [unknown, ...infer PRest] ? Args extends [unknown, ...infer ARest] ? DropPrefix : Args : Args; function curryMulti( fn: (...args: Args) => Return ): CurriedMulti { function curried(...args: unknown[]): unknown { if (args.length >= fn.length) { return fn(...(args as Args)); } return (...more: unknown[]) => curried(...args, ...more); } return curried as CurriedMulti; } // --- Usage --- function add3(a: number, b: number, c: number): number { return a + b + c; } const curriedAdd3 = curryMulti(add3); curriedAdd3(1)(2)(3); // ✅ 6 — one argument at a time, like the original curriedAdd3(1, 2)(3); // ✅ 6 — two arguments, then one curriedAdd3(1, 2, 3); // ✅ 6 — all three arguments at once curriedAdd3(1)(2, 3); // ✅ 6 — one, then two WHY THIS WORKS -------------- - CurriedMulti's call signature accepts a generic `Prefix` tuple bounded by `Partial & unknown[]` (a variable-length prefix of the remaining arguments) instead of always exactly one argument — this is what allows `curriedAdd3(1, 2)` to type-check, unlike the chapter's version which only ever accepted a single argument per call. - DropPrefix recurses through both tuples together (much like Challenge 2's Zip) to compute exactly what remains of Args once Prefix's elements have been consumed — that remainder becomes the Args for the next recursive CurriedMulti call. - The runtime curried() function doesn't need to know the exact tuple shapes at all — it just accumulates arguments and calls the original fn once enough have arrived (checked via fn.length, the number of declared, non-rest parameters) — the type-level machinery is what makes every intermediate call correctly typed despite the flexible arity.