TSでジェネリクスを用いない型レベル関数
ジェネリックな型を引数に渡したいときって、型をちゃんと抽象的に扱いたいときに割と遭遇すると思ってたので、実用性がありそう
TSでは、ジェネリックな型を型引数に使えない
code:ts
type Hoge<T,X> = T<X> extends string ? true : false
Type 'T' is not generic.(2315)
以下を使って、ジェネリクスを使わずに!!型レベル関数を表現する
インターフェースのマージ
交差型
code:ts
// interfaceのマージと交差型を用いた、ジェネリクスを用いない型レベル関数
interface TLF {
X: unknown;
Y: unknown;
}
type TLFA<F extends TLF, X> = (F & { X: X })"Y"; // Arrayを上のスタイルで定義
interface ArrayTLF extends TLF {
}
// 使う
type booleans = TLFA<ArrayTLF, boolean>; // boolean[]
type strings = TLFA<ArrayTLF, string>; // string[]
// 上記のスタイルの型レベル関数は、コレ自体がジェネリクスを使ってないので、ジェネリクスの引数として渡したり、extendsの制約に使える
type Hoge<T extends TLF ,X> = TLFA<T, X> extends string[] ? true : false
type H1 = Hoge<ArrayTLF, string> // true
type H2 = Hoge<ArrayTLF, boolean> // false
このテクニックは、先行事例としてはインターフェースのマージを用いて高階型を疑似的に再現しているfp-tsというライブラリがある その上で、記事の筆者が交差型を合わせて実現することを思いついたそうだ
miyamonz.iconが質問した
zennの記事より後発だとおもうが、やってる人がいた
ライブラリとして色々充実させている
ただし、そもそもが型パズルなんで、そんなにちゃんとライブラリ使う機会あるかは知らん
でも提供したいライブラリに強力な型支援してあげたいとき欲しくなったりするかも…
さらに、記事で使われてるものを改善した
Xに制限をかけることが可能
Applyの型引数XにX extends F["X"]をつけた
code:ts
// type level function
// interfaceのマージと交差型を用いた、ジェネリクスを用いない型レベル関数
interface TLF {
X: unknown; //input
Y: unknown; //output
}
// type ApplyTLF<F extends TLF, X> = (F & { X: X })"Y"; type ApplyTLF<F extends TLF, X extends F"X"> = (F & { X: X })"Y"; // Arrayを上のスタイルで定義
// X -> Array<X>
interface ArrayTLF extends TLF {
}
// X extends {name: string} -> X"name" interface GetNameTLF extends TLF {
X: {name: string}
}
type A1 = ApplyTLF<GetNameTLF, {name: 'hoge'}>
type A2 = ApplyTLF<GetNameTLF, {namae: 'hoge'}> // error
このように、Xが制約を満たさないときにちゃんとエラーが出る
code:ts
type MapTLF<F extends TLF,T extends F"X"[]> = { }
// get name
type GetNameFromArr<Arr extends {name: string }[]> = MapTLF<GetNameTLF, Arr>
//unbox
interface UnboxTLF extends TLF {
X: {value: unknown};
}
type UnboxAll<Arr extends {value: unknown}[]> = MapTLF<UnboxTLF, Arr>
MapTLFを定義して、配列にmapするのをDRYに書ける
メモ
カリー化されたTLFにまとめて適用したい
code:ts
interface TLF {
X: unknown;
X2:unknown;
Y: unknown;
}
type TLFA<F extends TLF, X extends F'X'> = (F & { X: X })"Y"; type Apply1<F extends TLF, X extends F'X'> = (F & { X: X })"Y"; type Apply<F extends TLF , Params extends unknown[] > =
Params extends [infer H extends F'X', ... infer Rest] ? Apply_<F,H, Rest>
: never;
type Apply_<F extends TLF, X extends F'X', Rest extends unknown[]> = Apply1<F,X> extends TLF
?
Rest extends [infer H extends Apply1<F,X>'X', ... infer _Rest] ? Apply_<Apply1<F,X>, H, _Rest>
: never
// Apply<Apply1<F,X>, Rest>
: Apply1<F,X>
interface PromiseTLF extends TLF {
}
type PromiseOfNumber = TLFA<PromiseTLF, number>;
type A<Key extends string,X> = {K in Key: X} // A -> B -> (_:A)=>B
interface F extends TLF {
}
interface F2<ARG> extends TLF {
}
//^?
type AP = TLFA<F,string>
type StringToNumber1 = TLFA<TLFA<F,string>, number>;