型変数 (type variables)
ここでは、ジェネリクスで重要な概念となる「型変数」とは何なのかについて説明します。
型変数
「変数」といえば、値を代入する入れ物をイメージすることでしょう。たとえば、次のコードはx
が変数で、その値に1
を代入しています。
ts
constx = 1;
ts
constx = 1;
一度変数を定義すれば、あちこちに値を書き写す必要がなくなり、その変数を使ってさまざまな処理をすることができます。
変数という便利な入れ物があるおかげで、繰り返し同じコードを書く必要がなくなったり、より抽象的なコードを書けるようになったりと、さまざまな恩恵を享受できるわけです。
型変数(type variables)は、もうひとつの便利な入れ物です。ただし、入れられるのは「値」ではなく「型」という違いがあります。
ts
functionprintAndReturn <T >(value :T ):T {console .log (value );returnvalue ;}
ts
functionprintAndReturn <T >(value :T ):T {console .log (value );returnvalue ;}
このprintAndReturn
関数のT
が型変数です。<T>
が型変数名を決めている部分でvalue
の型に使われているT
と戻り値に書かれているT
は変数を利用している部分、つまり、参照している部分です。
printAndReturn
関数のT
の変数スコープは、この関数の範囲になります。したがって、関数のシグネチャでT
を参照できるのはもちろん、関数の処理部分でも参照することができます。一方で、関数の外から参照することはできません。
ts
functionprintAndReturn <T >(value :T ):T {letvalues :T [] = []; // OKconstdoSomething = (value :T ) => {}; // OKreturnvalue ;}letCannot find name 'T'.2304Cannot find name 'T'.value :; T
ts
functionprintAndReturn <T >(value :T ):T {letvalues :T [] = []; // OKconstdoSomething = (value :T ) => {}; // OKreturnvalue ;}letCannot find name 'T'.2304Cannot find name 'T'.value :; T
この関数を利用するコードは、number
などT
に好きな型を代入することができます:
ts
constvalue =printAndReturn <number>(123);
ts
constvalue =printAndReturn <number>(123);
型引数
型引数(type arguments)とは、型変数に代入した型のことを言います。次のコードではnumber
が型引数です。
ts
constvalue =printAndReturn <number>(123);
ts
constvalue =printAndReturn <number>(123);
TypeScriptでは、型引数にも型推論が行われます。型引数推論(type argument inference)と言われます。上の例では、型変数T
にnumber
を代入するコードを明示的に書いていますが、変数の123
から型変数T
の型はnumber
型になることがコンパイラからは推測可能なので、次のコードのように型引数の記述を省略することもできます。
ts
constvalue =printAndReturn (123);
ts
constvalue =printAndReturn (123);
型変数に使える文字
TypeScriptの型変数に使える文字は変数名や関数名、クラス名などに使える文字種と同じものが使えます。したがって、大文字アルファベット1文字でなければならないといった制約はありません。
ts
functionfunc1 <T >(x :T ) {}functionfunc2 <Foo >(x :Foo ) {}functionfunc3 <fooBar >(x :fooBar ) {}functionfunc4 <$ >(x :$ ) {}functionfunc5 <かた >(x :かた ) {}
ts
functionfunc1 <T >(x :T ) {}functionfunc2 <Foo >(x :Foo ) {}functionfunc3 <fooBar >(x :fooBar ) {}functionfunc4 <$ >(x :$ ) {}functionfunc5 <かた >(x :かた ) {}
このように、3文字のFoo
、キャメルケースのfooBar
、記号の$
、Unicodeのかた
も型変数名として定義可能です。
ts
function func1<1>(x: 1) {} // コンパイルエラーfunction func2<class>(x: class) {} // コンパイルエラー
ts
function func1<1>(x: 1) {} // コンパイルエラーfunction func2<class>(x: class) {} // コンパイルエラー
型変数名に使えないものは数字ではじまるもの、class
などの予約語です。
型変数名の慣習
TypeScriptの慣習として、型変数名にはT
を用いることが多いです。このT
はtemplateの略と言われています。
単純なジェネリクスで、型変数が2つある場合は、T
とU
が用いられることがあり、その理由はアルファベット順でTの次がUだからです。この規則にしたがって、3つ目の型変数はV
とする場合もあります。
ts
functioncompare <T ,U >(a :T ,b :U ) {}
ts
functioncompare <T ,U >(a :T ,b :U ) {}
複数の型変数がある場合、型変数に意味のある名前をつけることもあります。その場合、TKey
、TValue
のようにT
接頭辞を付けた命名規則がしばしば使われます。ただし、これは「型変数名にはT
を用いる」という慣習ほどは一般的でないように思われます。
ts
functionmakeKeyValuePair <TKey ,TValue >(key :TKey ,value :TValue ) {}
ts
functionmakeKeyValuePair <TKey ,TValue >(key :TKey ,value :TValue ) {}
型変数名を単語にする場合は、大文字始まりのキャメルケースにすることが普通です。
型変数の名づけを巡る議論
「型変数はどのような名づけがベストか?」というテーマについては、プログラマーによってさまざまな主張があり、見解が分かれるところです。ここでは、参考までにその議論を取り上げますが、どうか混乱しないで頂ければと思います。実務においては、プロジェクトで一貫した命名規約に準ずることを意識しておけばいいでしょう。
できるだけ意味のある単語にすべきという主張
T
のような1文字だけでは何が入るのか分かりにくいため、意味のある単語にすべきという考え方があります。Array<T>
の代わりに、Array<Element>
のほうが分かりやすいとする立場です。
1文字のほうがよいとする主張
ジェネリクスはそもそも抽象的なものごとを扱うため、具体的な名前が付けられないため、意味を持たないTのほうがいいという考え方があります。
Element
のような型変数名を定義すると、一見するとそれがクラス名のように見えてしまいます。混乱を避けるためにもT
のほうがよいという意見があります。
型変数のスコープの広さによって使い分けるべきとする主張
プログラミングでの名づけは、名つけるもののスコープが広くなるほど長い名前を、狭くなるほど短い名前をつけるというテクニックがあります。たとえば、for
ループで変数にi
を用いることがありますが、これは慣習というところもありますが、変数のスコープが狭いという一面もあります。型変数にも同じことが言えて、型変数のスコープが広いものは単語にし、短いジェネリック関数の中でしか使わない型変数は1文字が妥当という考え方があります。