動機
タプル型を連結したくなるときはありませんか?例えば以下のようなケースを考えます
// 2つタプルが存在しています const t1 = [1, "aaa"] as const; // readonly [1, "aaa"] 型 const t2 = [true, () => {}] as const; // readonly [true, () => void] 型 // 繋げたい const result = [...t1, ...t2];
ですが、 result
はそれぞれのタプル型の情報がなくなり、要素の型のユニオン型の配列になってしまいます
これではタプルでせっかくN番目の要素はT型として推論して欲しいのに不便です。そこで、任意のタプルを任意個与えると、それらを要素の型を保持して連結したタプル型を返す型をつくりました。
実装
// ライブラリ側 // https://qiita.com/hrsh7th@github/items/84e8968c3601009cdcf2 type Head<T> = T extends readonly [infer H, ...readonly any[]] ? H : never; type Tail<T> = T extends readonly any[] ? ((...args: T) => any) extends (head: any, ...args: infer R) => any ? R : never : never; type Add<T extends readonly any[], E> = ((e: E, ...t: T) => any) extends ( ...a: infer U ) => any ? U : never; // (名前適当すぎる...) type Iter2<T extends readonly any[], Result extends readonly any[] = []> = { 0: Result; 1: Iter2<Tail<T>, Add<Result, Head<T>>>; }[T['length'] extends 0 ? 0 : 1]; // https://stackoverflow.com/questions/54607400/typescript-remove-entries-from-tuple-type type Iter< T extends readonly any[], T2 extends readonly any[], N extends number, Result extends readonly any[] = [] > = { 0: Result; 1: Iter<Tail<T>, Tail<T>[0], Tail<T>["length"], Result>; 2: Iter<T, Tail<T2>, N, Add<Result, Head<T2>>>; }[T["length"] extends 0 ? 0 : T2["length"] extends 0 ? 1 : 2]; // これを作りました! type ConcatTuple<T extends readonly any[]> = Iter2<Iter<T, T[0], T["length"]>>; const concatTuple = <T extends readonly (readonly [...any[]])[]>(...args: T): ConcatTuple<T> => [].concat(...args) as any; // 比較用にConcatTupleを使ってない普通の連結関数 const concatTuple2 = <T extends readonly (readonly [...any[]])[]>(...args: T)=> [].concat(...args); // 使う側 const t1 = [1, "aaa"] as const; // readonly [1, "aaa"] 型 const t2 = [true, () => {}] as const; // readonly [true, () => void] 型 const result = concatTuple(t1, t2); // [1, "aaa", true, () => void, {}] 型 const result2 = [...t1, ...t2]; // (true | 1 | "aaa" | (() => void))[] 型
(Playgroundのリンク貼ろうとしたのですが、URL長すぎてはてなブログの編集画面がフリーズしたので諦めました)
これで連結後のタプルは、ちゃんと0番目はnumberとして推論されますし、3番目は関数として推論されます。
悲しみ
めでたしめでたし…といきたかったのですが、ロジックは正しいのでIDE上では正しい型が表示されてますが、どうやらtscのコンパイル時の型のインスタンス化の再帰上限に引っかかってしまうようでコンパイルは出来ませんでした…