Exploring a solution utilizing a workaround that defines generic functions does not seem feasible in this case. While the workaround transforms a generic call signature into a generic type, it does not facilitate the transformation of one generic call signature into another, especially when dealing with multiple possible ones passed to curried()
. Therefore, this approach is deemed unviable.
Although there is some support for inferring generic functions, its functionality is limited and fragile. It fails when generating overloaded functions, as seen in your implementation with the output of curried()
. Refer to microsoft/TypeScript#33594 for further details. As of now, achieving overloads from curried()
remains unattainable.
If we relax the requirement for overloads and focus on simply removing one parameter from the function's end, the call signature for curried()
can be formulated like so:
declare function curried<I extends any[], L, R>(fn: (...args: [...I, L]) => R):
((...args: I) => (last: L) => R)
This can be verified through an example:
function z<T>(a: T, b: T, c: T) { return [a, b, c]; }
const cZ = curried(z);
// const cZ: <T>(a: T, b: T) => (last: T) => T[]
const cz2 = cZ(5, 6);
// const cz2: (last: number) => number[]
The essence of generality prevails here. The function z
is generic, hence cZ
also inherits this quality and allows for specifying T
.
It's important to note that the demonstration does not involve your a
function. Let's try using it:
function a<T>(b: string, c: number, d: T[]) { return [b, c, ...d]; }
const f = curried(a);
// const f: <T>(b: string, c: number) => (last: T[]) => (string | number | T)[]
const f2 = f('hi', 42);
// const f2: (last: unknown[]) => unknown[]
The resulting f
proves to be generic, but lacks proper inference due to the absence of context determining the type parameter
T</code. Consequently, the compiler defaults to <code>unknown
.
To overcome this, you may manually specify T
when calling
f</code, diminishing its generic nature slightly:</p>
<pre><code>const f3 = f<number>('hi', 42)
// const f3: (last: number[]) => (string | number)[]
An idealized generic output for curried(a)
would redefine T
scope within the returned function:
declare const f: (b: string, c: number) => <T>(last: T[]) => (string | number | T)[]
This adjustment would yield another generic function upon calling f()
:
const f2 = f('hi', 42);
// const f2: <T>(last: T[]) => (string | number | T)[]
Hence, eliminating instances of unknown
:
const result = f2([1, 2, 3]);
// const result: (string | number)[]
Regrettably, the current TypeScript capabilities fall short in enabling automatic extension of generic type parameters beyond the initial scope. More advanced approaches such as higher kinded types, generic values, or existentially quantified generics are required to achieve this complex functionality.
In conclusion, while compromising on certain criteria may bring you closer to your goal, fully meeting all requirements might necessitate future TypeScript versions integrating enhanced manipulation features for generic function types.
Access the code on TypeScript Playground