【ITニュース解説】Type Variance Clearly
2025年09月17日に「Dev.to」が公開したITニュース「Type Variance Clearly」について初心者にもわかりやすく解説しています。
ITニュース概要
型変性とは、プログラミングのジェネリック型で、親子関係にある型がどこまで置き換え可能かを定義するルールだ。出力で子型を親型に置き換えられる共変性、入力で親型を子型に置き換えられる反変性、厳密な一致が必要な不変性、双方向で置き換え可能な両変性がある。
ITニュース解説
型分散とは、プログラミングにおける型理論の概念の一つで、異なる型同士がどのような条件で互いに置き換え可能であるかを定義するものだ。特に、継承関係にある親子型(より広い親型とより狭い子型)の間で、いつ子型を親型の代わりに使えるのか、あるいはその逆が可能なのか、といった関係を記述する。この考え方は、ジェネリック型(型をパラメータとして受け取る型)を扱う際に非常に重要になる。複雑に聞こえるかもしれないが、具体的な例を通して見ていけば、その意味と利点が理解できるだろう。型分散には主に共変性、反変性、不変性、双変性の4種類がある。
共変性は、最も直感的で理解しやすい型分散の関係だ。これは、より狭い子型が、より広い親型が期待される場所で使用できることを意味する。例えば、動物(Animal)の代わりに猫(Cat)を使うことができる関係を考えるとわかりやすい。猫は動物の一種なので、「動物を扱う」という文脈では常に「猫を扱う」ことも可能だ。しかし、その逆は成り立たない。すべての動物が猫であるわけではないから、「猫を扱う」という文脈で「どんな動物でも扱う」ことはできない。この関係は、関数の「戻り値」(出力)の位置でよく見られる。例えば、「動物を返す関数」の代わりに「猫を返す関数」を代入できる。猫を返す関数は必ず動物を返すので、問題なく動作する。しかし、動物を返す関数を「猫を返す関数」の代わりに代入することはできない。なぜなら、その動物が猫ではない可能性もあるため、期待される「猫」が得られないかもしれないからだ。つまり、共変性は「子型は親型の出力として安全に扱える」というルールだと理解できる。
反変性は共変性とは反対の関係で、少し理解が難しいかもしれない。これは、より広い親型が、より狭い子型が期待される場所で使用できることを意味する。具体例として、食べ物の処理を考えてみよう。どんな動物の食べ物でも処理できる「汎用的な動物フードプロセッサ」と、猫の食べ物だけを処理する「猫フードプロセッサ」があるとする。この場合、「猫の食べ物を処理する」という文脈で、「汎用的な動物フードプロセッサ」を使うことは可能だ。なぜなら、汎用プロセッサは猫の食べ物も動物の食べ物として問題なく処理できるからだ。しかし、「どんな動物の食べ物でも処理する」という文脈で、「猫フードプロセッサ」を使うことはできない。猫フードプロセッサは猫の食べ物しか扱えず、他の動物の食べ物には対応できない可能性があるためだ。この関係は、関数の「引数」(入力)の位置でよく見られる。例えば、「動物を受け取って処理する関数」は、「猫を受け取って処理する関数」の代わりに代入することができる。これは、動物を受け取れる関数は猫も動物として受け取って処理できるため問題ない。一方で、猫を受け取れる関数を「動物を受け取って処理する関数」の代わりに代入することはできない。なぜなら、猫を受け取れる関数は猫以外の動物を適切に処理できない可能性があるからだ。つまり、反変性は「親型は子型の入力として安全に扱える」というルールだと理解できる。
不変性は、共変性や反変性とは異なり、型同士の置き換えができない関係を指す。これは最も厳格なルールで、期待される型と全く同じ型のインスタンスしか受け入れない。つまり、親型も子型も、互いに置き換えることはできない。例えば、ゴミの分別を考えると分かりやすい。一般的な「廃棄物(Waste)」と、特定の種類の「生ゴミ(FoodWaste)」があるとする。生ゴミは廃棄物の一種だが、生ゴミを「燃えないゴミ」の容器に入れることはできないし、逆に一般的な廃棄物を「生ゴミ」の容器に入れることもできない。それぞれのゴミは、その種類に特化した特定の容器にしか捨てられない。プログラミングにおける不変性も同様で、ジェネリック型が入力と出力の両方の位置でその型を使用する場合に発生しやすい。例えば、ある型を受け取り、同じ型を返す関数型の場合、猫を扱う型と動物を扱う型は互いに置き換えできない。不変性は、型の厳密な一致が必要とされる場合に適用される関係だ。
双変性は不変性の対極にあり、完全に型同士が交換可能であることを意味する。つまり、型Aを型Bで置き換えたり、その逆を行ったりすることがどちらも可能となる関係だ。TypeScriptのような特定のプログラミング言語では、このような挙動が見られることがある。例えば、TypeScriptのメソッドパラメータは、デフォルトで双変性を持つ場合がある。これは、理論的には厳密ではないものの、開発者にとってより柔軟性を提供するという設計上の選択によるものだ。例えば、ある型を処理するメソッドを持つ型を考える。猫を扱うメソッドを持つオブジェクトを動物を扱うメソッドを持つ変数に代入しても問題なく、その逆も可能であるという状況を示す。ただし、このような双変性は意図しない挙動につながる可能性もあるため、TypeScriptではinキーワードを使って明示的に反変性として宣言し、より厳密な型チェックを行うこともできる。in Vとすることで、メソッドパラメータが厳密に反変性として扱われ、より安全なコードを記述できるようになる。これらの型分散の概念は、特にオブジェクト指向プログラミングや関数型プログラミングにおいて、ジェネリック型や高階関数を扱う際の型の安全性と柔軟性を理解するために不可欠だ。