【入門編】Hackの『Contravariance(反変)』と『Covariance(共変)』:ジェネリクスにおける継承のルール – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

Hack言語の世界へようこそ。HHVMの深淵を覗き、静的型システムの美学に触れようとしている君なら、きっと「ジェネリクス」の壁に一度はぶつかったことがあるはずだ。

「なぜ `Vector` に `Vector` を代入できないのか?」

この疑問こそが、Hackの型システムが君のコードをバグから守ろうとしている防波堤なんだ。今日は、ジェネリクスにおける共変(Covariance)と反変(Contravariance)という、一見難解な概念を、我々のコードを堅牢にするための「武器」に変えていこう。

—

1. なぜ「代入できない」のか?(型安全のジレンマ)

まずは、直感的に「おかしい」と感じる例を見てみよう。

<<__EntryPoint>>
function main(): void {
// DogはAnimalを継承しているとする
Vector $dogs = Vector[new Dog()];

// もし、これが許されたらどうなる?
// Vector $animals = $dogs;

// $animals.add(new Cat()); // ここで問題発生!
// 実際の中身は $dogs なのに、そこに Cat が混入してしまった!
}

もし `Vector` を `Vector` として扱えてしまうと、後から `Cat` を追加できてしまうよね。実行時に `Dog` を期待している場所で `Cat` が飛び出してきたら、HHVMはクラッシュを避けるためにパニックを起こす。

これを防ぐのが、Hackの不変(Invariance)というデフォルトルールなんだ。「`T` が同じでない限り、代入は許さない」という厳格なガードだね。

—

2. 共変(Covariance):読み取り専用の「優しい世界」

「取り出すだけなら、継承関係を維持しても安全だよね?」というのが共変の考え方だ。`+` 記号を使って表現するよ。

interface Readable<+T> {
function get(): T;
}

function processAnimals(Readable $source): void {
// Animalとして読み取れるなら、DogでもCatでも問題ないよね
$item = $source.get();
// …
}

  • 共変 (`+T`): 「Tそのもの、あるいはTのサブタイプを返してもいいよ」という約束。
  • ルール: 戻り値の位置(アウトプット)にしか使えない。

君がもしデータを「供給する側」のインターフェースを作っているなら、共変を使うことで、利用側の柔軟性が劇的に向上するんだ。

—

3. 反変(Contravariance):書き込み専用の「懐の深い世界」

逆に、データを「受け取る側」ならどうだろう? `Animal` を受け取れるなら、より広範な `Animal` を扱えるものは、`Dog` を受け取れるはずだよね。これが反変。`-` 記号を使う。

interface Consumer<-T> {
function consume(T $item): void;
}

function feedDog(Consumer $dogFeeder): void {
// Dogを期待している場所に、Animalなら何でも食えるConsumerを渡せる
$dogFeeder.consume(new Dog());
}

  • 反変 (`-T`): 「Tそのもの、あるいはTのスーパタイプ(親)でも受け入れられるよ」という約束。
  • ルール: 引数の位置(インプット)にしか使えない。

「何でも食べるよ」という `Consumer` は、`Dog` しか食べない場所でも立派に役割を果たせる。これが反変の魔法だ。

—

4. 現場で陥りやすい「コンパイルエラー」の正体

Hackの型チェッカーが「Contravariance/Covariance mismatch」と叫ぶとき、それは君が「読み取り専用の場所に書き込み可能なメソッドを置いている」か、その逆をやっているときだ。

// これはコンパイルエラーになる!
interface BadContainer<+T> {
function set(T $item): void; // 共変なのに引数に使っている!
}

なぜなら、`+T`(共変)と宣言した時点で、型チェッカーは「この型は読み取り専用だ」と判断する。そこに `set` という「書き込みメソッド」を混ぜると、型安全の契約が崩壊するからだね。

—

まとめ:マスターへの道

ここを理解できれば、君はもうHackの型システムの半分を掌握したも同然だ。

1. 不変 (`T`): 読み書き両方やる。一番安全で堅い。
2. 共変 (`+T`): 読み取り専用。`T` のサブクラスもOK。
3. 反変 (`-T`): 書き込み専用。`T` の親クラスもOK。

この `+` と `-` を適切に使い分けるだけで、君の書くコードは驚くほど汎用的になり、かつHHVMの強力な最適化の恩恵を最大限に受けられるようになる。

ジェネリクスは単なる型合わせの道具じゃない。「データの流れをどう制約するか」を設計する強力なアーキテクチャツールなんだ。自信を持って、コードを書いていこう。君なら絶対にできるよ!

タイトルとURLをコピーしました