はじめに
配列の長さを静的に決めておきたいことがよくあります。例えば9×9のリバーシの盤面を表現したい場合、長さ9のnumber
(1マスの中の情報はnumber
で表すとして)の配列を、9個用意することになり、そこからはみ出る参照は、実行前に取り除いておきたいものです。
const board: [固定長配列の上手い型定義] = [[0,0,0,0,0,0,0,0,0], ... , [0,0,0,0,0,0,0,0,0]]
console.log(board[4][5]) // <- 0
console.log(board[9][0]) // <- 9×9の範囲を超えているので、コンパイル時にエラーが出るようにしたい
「長さ9のnumber
の配列」は、TypeScriptの「タプル」を使えば、次のように表すことができます:
const row: [number, number, number, number, number, number, number, number, number] = [0,0,0,0,0,0,0,0,0]
じゃあリバーシの盤面の型はこう?
type Row = [number, number, number, number, number, number, number, number, number]
type Board = [Row, Row, Row, Row, Row, Row, Row, Row, Row]
…こんな汚ねえコードは書きたくねえ!!!!!ってことで、以下のよう 「固定長な配列」を「再帰」を使って表現してみます:
結論
type FixedLengthArray<T, N extends number, A extends any[] = []> = A extends { length: N } ? A : FixedLengthArray<T, N, [ ...A, T ]>
配列の要素の型引数 T
, 配列の長さを表す型引数 N
, 配列の蓄積を表す型引数 A
に対して、Conditional Typesを使ってA
が長さN
に達しているかどうか判定します。既にN
に達していた場合はそのままA
を返せばよく、達していなかった場合は再帰的にFixedLengthArray
を呼んで配列の蓄積の部分を引き伸ばしていくイメージです。
これを使えば、リバーシの盤面は次のように簡潔に表せますね!
type Row = FixedLengthArray<number, 9>
type Board = FixedLengthArray<Row, 9>
// もしくはRowをかませず単純に
type Board = FixedLengthArray<FixedLengthArray<number, 9>, 9>
TypeScriptで、楽しい型パズルライフを送りましょう!
参考文献