型パズルとは

型パズルの例

以下に、TypeScript の型パズルの例を 3 つ紹介します。

① 関数の引数と戻り値の型を指定し、指定された仕様を満たすプログラムを作成する問題。例えば、「引数として 2 つの整数を受け取り、その 2 つの整数を足した値を返す関数を作成しなさい」。

① 型エイリアスを使用して、複雑な型を定義し、その型を指定された仕様に沿って操作する問題。例えば、「型エイリアスを使用して、配列内の各要素が数値であることを表す型 ArrayOfNumbers を定義しなさい。そして、ArrayOfNumbers 型の値を受け取り、各要素の合計値を返す関数を作成しなさい」。

③ ジェネリックを使用して、汎用的な型を定義し、その型を指定された仕様に沿って操作する問題。例えば、「ジェネリックを使用して、配列内の各要素が同じ型であることを表す型 SameTypeArray<T> を定義しなさい。そして、 SameTypeArray<T> 型の値を受け取り、各要素の合計値を返す関数を作成しなさい」。

解答

function sum(x: number, y: number): number {
  return x + y;
}

type ArrayOfNumbers = number[];

function sumArray(numbers: ArrayOfNumbers): number {
  let sum = 0;
  for (const num of numbers) {
    sum += num;
  }
  return sum;
}

type SameTypeArray<T> = T[];

function sumArray(numbers: SameTypeArray<number>): number {
  let sum = 0;
  for (const num of numbers) {
    sum += num;
  }
  return sum;
}

型パズルの非直感的な挙動

型パズルがパズルとして成り立つのは、言ってしまえば、型システムが人間の直感とことなるからです。 仮に、素直におもいついた型定義が、そのままtypescriptのコンパイラにとって解釈可能で正しいものであれば、パズルとし機能しないことを考えれば逆説的に明らかです

TypeScript の型システムは、型推論に基づいて型を推論することができます。型推論とは、プログラム中における値の型から、変数や関数の型を推測することです。これは、プログラマが型を明示的に定義する必要がなくなるため、プログラミングが楽になるという利点があります。 しかし、人間が直感的に想像する型推論と、TypeScript が行う型推論とは、必ずしも一致しない場合があります。そのため、TypeScript の型パズルを解く際には、型推論が行われる順序や方法を理解することが重要です。 以下に、人間が直感的に想像する型推論と、TypeScript が行う型推論が異なる例を紹介します。

let x: number | string;
x = "hello";
x = x.concat(" world");
console.log(x.length);

この問題では、変数 x の型が number | string と定義されています。まず、文字列の値 "hello" を代入しているため、変数 x の型は string と推定されます。次に、concat メソッドを使って文字列 " world" を連結しているため、変数 x の型は、連結した結果として string 型の値が得られます。

しかし、次の行では、変数 x の length プロパティを出力しています。このプロパティは、文字列型の値にしか存在しないため、型推論により、変数 x の型は string と推定されます。しかし、人間がこの問題を読む際には、変数 x の型が string と推定されることは直感的ではありません。

このように、TypeScript の型推論が人間にとって直感的でない場合があります。そのため、TypeScript を使ったプログラミングでは、型宣言を適切に使い、型推論の挙動を理解しておくことが重要です。