I have defined pipe
and compose
functions, each able to compose exactly two functions into a new one, with the only difference of the call order upon application to the argument.
Both functions are generic and I was hoping I can make the product-function to be strongly-typed, yet something is off and the result function type is inferred incorrectly.
Questions:
function pipe<X, Y, Z>(first: (x: X) => Y): (f2: (y: Y) => Z) => (x: X) => Z {
return function (second: (y: Y) => Z): (x: X) => Z {
return function (x: X): Z {
return second(first(x));
};
}
}
function compose2<X, Y, Z>(second: (y: Y) => Z): (f2: (x: X) => Y) => (x: X) => Z {
return function (first: (x: X) => Y): (x: X) => Z {
return function (x: X): Z {
return second(first(x));
};
}
}
Usage
function numToStr(n: number): string { return n.toString(); }
function strToLen(s: string): number { return s.length; }
// Inferred as (x: number) => unknown
// Should be (x: number) => number
const f1 = pipe(numToStr)(strToLen);
// Inferred as(x: unknown) => number
// Should be (x: number) => number
const f2 = compose(strToLen)(numToStr);
Instead of hard coding the return type of each function. Allow typescript to infer it and ensure the generic parameters are used not too early:
function pipe<X, Y>(first: (x: X) => Y) { // <-- Avoid <Z> here is too early
return function <Z>(second: (y: Y) => Z) { // <-- <Z> is needed here
return function (x: X) {
return second(first(x));
};
}
}
function compose<Y, Z>(second: (y: Y) => Z) {// <-- Avoid <X> here is too early
return function <X>(first: (x: X) => Y) {// <-- <X> is needed here
return function (x: X): Z {
return second(first(x));
};
}
}
function numToStr(n: number): string { return n.toString(); }
function strToLen(s: string): number { return s.length; }
const f1 = pipe(numToStr)(strToLen);
const f2 = compose(strToLen)(numToStr);
The problem is that in your first call pipe(numToStr)
TypeScript can infer x
from n
as number
and Y
from the return as string
but there is no Z
at this point so it is infered as unknown
. TypeScript don't try to re-infer it later when the second call happens, it stays as unknown
.