Reputation: 327
lets imagine type T:
type T = {
prop1: (s: S) => T1,
prop2: (s: S) => T2,
prop3: (s: S) => T3,
}
and now lets imagine type W
type W = (s: S) => {
prop1: T1,
prop2: T2,
prop3: T3,
}
its easy to write a function that maps T to W by hand,
is it possible to write generic type sefe version of it in typescript?
function x(t: T): W {
return funtion(s: S) {
prop1: t.prop1(s),
prop2: t.prop2(s)
prop3: t.prop3(s)
}
}
what kind of feature language is missing to facilitate this, something like higher order generic types?
Upvotes: 1
Views: 565
Reputation: 328302
You can indeed write a generic version of this in TypeScript:
function x<S, V>(t: {[K in keyof V]: (s: S) => V[K]}): (s: S) => V {
return function(s: S): V {
const ret = {} as V;
Object.keys(t).forEach((k: keyof V) => {
ret[k] = t[k](s);
})
return ret;
}
}
const xSpecific: (t: T) => W = x; // okay
Note that V
is the return type of your W
function. (So W
is essentially the same as (s: S) => V
.) And the input to x
is a mapped type corresponding to T
: it has the same keys as V
, but its values are functions from S
to the corresponding properties of V
.
You can get away with having the function input being a mapped type and the output being an unmapped one because TypeScript supports inference from mapped types. Otherwise you'd need something like the proposed "extended typeof
" feature to derive W
from T
generically. (This might be the missing language feature to which you're alluding.)
As for the implementation, I'm looping over the keys of t
and applying each function t[k]
to the input s
.
And xSpecific
is the same as x
narrowed to the particular T
and W
types you posted. This compiles because TypeScript recognizes that the generic x
is compatible.
Now for the caveats and fine print. Unfortunately, the compiler isn't able to reliably infer the type of S
from x
directly. If you just call the generic x()
with a T
input, you get this:
declare const t: T;
const w = x(t); // (s: {}) => { prop1: T1; prop2: T2; prop3: T3; }
The w
is not exactly a W
... it accepts any input, not just an S
. If you really need to narrow the type of input, you'll have to do it yourself by manually specifying the generic parameters:
const w = x<S, {prop1: T1, prop2: T2, prop3: T3}>(t);
which is ugly, or narrowing the resulting w
manually by assertion or annotation:
const w: W = x(t);
Anyway, hope that helps. Good luck!
Upvotes: 1