メインコンテンツまでスキップ

型変数 (type variables)

ここでは、ジェネリクスで重要な概念となる「型変数」とは何なのかについて説明します。

型変数#

「変数」といえば、値を代入する入れ物をイメージすることでしょう。たとえば、次のコードはxが変数で、その値に1を代入しています。

typescript
const x = 1;
typescript
const x = 1;

一度変数を定義すれば、あちこちに値を書き写す必要がなくなり、その変数を使ってさまざまな処理をすることができます。

変数という便利な入れ物があるおかげで、繰り返し同じコードを書く必要がなくなったり、より抽象的なコードを書けるようになったりと、さまざまな恩恵を享受できるわけです。

型変数(type variables)は、もうひとつの便利な入れ物です。ただし、入れられるのは「値」ではなく「型」という違いがあります。

typescript
function printAndReturn<T>(value: T): T {
console.log(value);
return value;
}
typescript
function printAndReturn<T>(value: T): T {
console.log(value);
return value;
}

このprintAndReturn関数のTが型変数です。<T>が型変数名を決めている部分でvalueの型に使われているTと戻り値に書かれているTは変数を利用している部分、つまり、参照している部分です。

printAndReturn関数のTの変数スコープは、この関数の範囲になります。したがって、関数のシグネチャでTを参照できるのはもちろん、関数の処理部分でも参照することができます。一方で、関数の外から参照することはできません。

typescript
function printAndReturn<T>(value: T): T {
let values: T[] = []; // OK
const doSomething = (value: T) => {}; // OK
}
let value: T; // Error
typescript
function printAndReturn<T>(value: T): T {
let values: T[] = []; // OK
const doSomething = (value: T) => {}; // OK
}
let value: T; // Error

この関数を利用するコードは、numberなどTに好きな型を代入することができます:

typescript
const value = printAndReturn<number>(123);
typescript
const value = printAndReturn<number>(123);

型引数#

型引数(type arguments)とは、型変数に代入した型のことを言います。次のコードではnumberが型引数です。

typescript
const value = printAndReturn<number>(123);
typescript
const value = printAndReturn<number>(123);

TypeScriptでは、型引数にも型推論が行われます。型引数推論(type argument inference)と言われます。上の例では、型変数Tnumberを代入するコードを明示的に書いていますが、変数の123から型変数Tの型はnumber型になることがコンパイラからは推測可能なので、次のコードのように型引数の記述を省略することもできます。

typescript
const value = printAndReturn(123);
typescript
const value = printAndReturn(123);

型変数に使える文字#

TypeScriptの型変数に使える文字は変数名や関数名、クラス名などに使える文字種と同じものが使えます。したがって、大文字アルファベット1文字でなければならないといった制約はありません。

typescript
function func1<T>(x: T) {}
function func2<Foo>(x: Foo) {}
function func3<fooBar>(x: fooBar) {}
function func4<$>(x: $) {}
function func5<かた>(x: かた) {}
typescript
function func1<T>(x: T) {}
function func2<Foo>(x: Foo) {}
function func3<fooBar>(x: fooBar) {}
function func4<$>(x: $) {}
function func5<かた>(x: かた) {}

このように、3文字のFoo、キャメルケースのfooBar、記号の$、Unicodeのかたも型変数名として定義可能です。

typescript
function func1<1>(x: 1) {} // コンパイルエラー
function func2<class>(x: class) {} // コンパイルエラー
typescript
function func1<1>(x: 1) {} // コンパイルエラー
function func2<class>(x: class) {} // コンパイルエラー

型変数名に使えないものは数字ではじまるもの、classなどの予約語です。

型変数名の慣習#

TypeScriptの慣習として、型変数名にはTを用いることが多いです。このTはtemplateの略と言われています。

単純なジェネリクスで、型変数が2つある場合は、TUが用いられることがあり、その理由はアルファベット順でTの次がUだからです。この規則にしたがって、3つ目の型変数はVする場合もあります。

typescript
function compare<T, U>(a: T, b: U) {}
typescript
function compare<T, U>(a: T, b: U) {}

複数の型変数がある場合、型変数に意味のある名前をつけることもあります。その場合、TKeyTValueのようにT接頭辞を付けた命名規則がしばしば使われます。ただし、これは「型変数名にはTを用いる」という慣習ほどは一般的でないように思われます。

typescript
function makeKeyValuePair<TKey, TValue>(key: TKey, value: TValue) {}
typescript
function makeKeyValuePair<TKey, TValue>(key: TKey, value: TValue) {}

型変数名を単語にする場合は、大文字始まりのキャメルケースにすることが普通です。

info

型変数の名づけを巡る議論#

「型変数はどのような名づけがベストか?」というテーマについては、プログラマーによってさまざまな主張があり、見解が分かれるところです。ここでは、参考までにその議論を取り上げますが、どうか混乱しないで頂ければと思います。実務においては、プロジェクトで一貫した命名規約に準ずることを意識しておけばいいでしょう。

できるだけ意味のある単語にすべきという主張#

Tのような1文字だけでは何が入るのか分かりにくいため、意味のある単語にすべきという考え方があります。Array<T>の代わりに、Array<Element>のほうが分かりやすいとする立場です。

1文字のほうがよいとする主張#

ジェネリクスはそもそも抽象的なものごとを扱うため、具体的な名前が付けられないため、意味を持たないTのほうがいいという考え方があります。

Elementのような型変数名を定義すると、一見するとそれがクラス名のように見えてしまいます。混乱を避けるためにもTのほうがよいという意見があります。

型変数のスコープの広さによって使い分けるべきとする主張#

プログラミングでの名づけは、名つけるもののスコープが広くなるほど長い名前を、狭くなるほど短い名前をつけるというテクニックがあります。たとえば、forループで変数にiを用いることがありますが、これは慣習というところもありますが、変数のスコープが狭いという一面もあります。型変数にも同じことが言えて、型変数のスコープが広いものは単語にし、短いジェネリック関数の中でしか使わない型変数は1文字が妥当という考え方があります。