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

Re:型と集合

なぜ急に集合の話をしたのか。結論から言うと、TypeScriptの型は集合そのものであるからです。

そして、この考え方ができると、関数の引数に渡せるかどうかや、あとで出てくるジェネリクスの上限についてなどでスッと理解することができます。

詳しく見ていきましょう。

例えばnumber型。number型の変数には数値のみを代入することができ、他のstring型の値や、配列型などといったものを代入することはできません。

const a: number = 3.1415926   // OK!
const b: number = "3.1415926" // NG!

これはなぜか。「型が違うから代入できない」、だとなんだかもの足りません。その証拠として以下のコードはコンパイルすることができます。

const a: 1 = 1
const b: number = a // OK!

bはnumber型なのに1型であるaの値を代入することができます。これでは「型が違うから代入できない」と言うだけでは説明がつきませんね?

ではどうしてnumber型の変数に"3.1415926"が代入できず、1型の値1を代入することができるのでしょうか?答えは、前者は集合numberの要素ではなく、後者は集合numberの要素であるからです。

改めてnumber型がなんであるかを再定義します。number型とは、全ての数値を要素とする集合です。

そして、代入可能であるとはなんであるか。この答えは代入する値が代入される変数の型(集合)の要素であると言うことです。

こう考えると「型が違うから代入できない」では説明できなかった部分も説明がつきます。1型の要素は1だけであり、これはnumberの要素でもあります。したがってnumber型の変数に対し1型の値を代入することができるのです。

但し一般的に代入可能であると言うこととしては先ほどの定義では不十分です。

では逆にこれはどうでしょうか?

const a: number = 1
const b: 1 = a

number型の値1は確かに1型の要素なので、代入できるはずです。

試しにコンパイルしてみましょう。

Type 'number' is not assignable to type '1'.

なんと。コンパイルエラーになってしまいました。これはなぜでしょうか?その原因は変数aがnumber型であることです。

値は一度変数に入れられると、「その型の値である」ということまでしかわからなくなります。したがって、aの中身は確かに1だとは言えず、2かもしれないし3かもしれない、はたまたもっと違う数値かもしれないということになります。

そしてもしaの中身が2だったら、2は1型の要素ではありません。中身がその型の要素じゃない可能性がある時点で代入が不可能になるわけです。

但しこれでは判定方法がふんわりしているしこの章のタイトルである「型と集合」の話として不十分です。

そこで少し一般化して、型Aの変数aと型Bの変数bがあってaの中身をbに代入することができるかについて考えましょう。

const a: A = A型のなにかしらの値
const b: B = a // これが可能かどうか

bに対してaが代入できると言うのは、「必ずaの中身が型Bの要素であること」と言えます。言い換えると「型Aの全ての要素が型Bの要素である」となります。

これは先ほどの集合の話で出てきた、「型Aが型Bの部分集合である」の定義(言い方はちょっと違う)そのものです。

すなわち、型Aを型Bに代入できるとは、型Aが型Bの部分集合であると言うことです。TSにおいては部分型(Sub type)などと呼びます。

これでこの章の内容として十分な判定方法を得ることができました。

ただし、any型についてはルールが存在せず、関数の型にはこのルールとは別のルールが存在するのでその際改めて説明します

まとめ

TypeScriptにおいて

  • 型とは集合のことである
  • 型A(の値)を型Bに代入できる = 型Aが型Bの部分集合である
  • ただしanyと関数の型については
    • anyにはルールがない
    • 関数の型には別のルールが存在する

ここまでの話についてこれなかった人も、これだけ覚えておいて!上のお話はこの結論を導くための回りくどい説明でしかありません!