この記事はTS3.4.3で動作確認をしています。
hyper(1,a,b)=a+b
hyper(2,a,b)=a*b
hyper(3,a,b)=a^b
︙
のような演算子です(雑)
詳しくはWikipediaを見て下さい。
定義だけ貼っておきます。
hyper(1,a,b)=a+b
hyper(_,a,1)=a
hyper(i,a,1)=hyper(i-1,a,hyper(i,a,b-1))
型レベルのタプル操作を行うので以下の記事を読んでおくといいかもしれません。
TypeScript 3.0のExtracting and spreading parameter lists with tuplesで遊ぼう
ちなみに今回は上の記事の型レベルタプル操作などをまとめた自作ライブラリtypepark
を使いますが機能は上の記事の通りです。
F
やG
が複雑な関数だと以下のような書き方をするとコンパイルエラーが発生することがあります。
type Hoge<T>=F<Cast<G<T>,any[]>>;
このような時は以下のように書き直すことで回避できます。
type Hoge<T>=F<G<T> extends infer X?Cast<X,any[]>:X>;
数値リテラルを直接扱うのは大変です。
そこでany
がN
個のタプル型を数値リテラル型のN
代わりに使う_Hyper
関数を実装しましょう。
例えば_Hyper<[any],[any,any],[any,any,any]>
は[any,any,any,any,any]
です。
これは定義通りですね。
ところどころコンパイルエラー回避のテクニックを使っているくらいです。
数値リテラルの代わりにタプルを使っているのでA-1
はTail<T>
に、A+B
はConcat<A,B>
と言った書き方が出来ます。
type _Hyper<I extends any[], A extends any[], B extends any[]> = {
0: Concat<A, B>,
1: A,
2: _Hyper<Tail<I>, A, _Hyper<I, A, Tail<B>> extends infer X ? Cast<X, any[]> : never>,
}[I extends [any] ? 0 : (B extends [any] ? 1 : 2)];
では数値リテラルを受け取り数値リテラルを返す関数はどう実装したらいいでしょうか?
タプル→数値リテラルと数値リテラル→タプルの変換が出来ればいいことが分かると思います。
前者はT["length"]
と書くだけなので簡単です。
後者は以下のようなRepeat
関数を使いましょう。
export type Repeat<T, N extends number, R extends any[]=[]> = {
0: R,
1: Repeat<T, N, Cons<T, R>>
}[R["length"] extends N ? 0 : 1];
これもtypepark
に含まれていますが、まだ解説してなかったので軽く解説しておきます。
要素T
をN
個持つタプルを返す関数です。
結果値がR
なのでR
の長さがN
になるまでT
をCons
しています。
これでRepeat<any,N>
と書くことで数値リテラル→タプルの変換が出来るようになりました。
ではHyper
関数の実装です。
export type Hyper<I extends number, A extends number, B extends number>
= _Hyper<Repeat<any, I> extends infer X ? Cast<X, any[]> : never, Repeat<any, A> extends infer X ? Cast<X, any[]> : never, Repeat<any, B> extends infer X ? Cast<X, any[]> : never> extends infer X ? Cast<X, any[]>["length"] : never;
ところどころコンパイルエラーを回避して実装しています。
Hyper<1, 1, 4>//5
Hyper<2, 5, 4>//20
Hyper<3, 2, 4>//16
Hyper<1, 100, 200>//数値が大きすぎてコンパイルエラー