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

Hackの型システムを掌握せよ:共変・反変がもたらす「壊れない」ジェネリクス設計

Hackの静的型システム(Strict Mode)と向き合うとき、多くのエンジニアが「なぜこの代入は許可されないのか?」という壁に突き当たる。その正体こそが、型理論における変位(Variance)だ。

我々がHHVM上で高パフォーマンスかつ堅牢なシステムを構築する際、ジェネリクスの扱いを誤れば、実行時のランタイムエラーを招くか、あるいは型チェッカーによって開発速度を無駄に削がれることになる。

今日は、コンテナ設計やAPIレスポンスのモデリングにおいて避けては通れない「共変・反変」を、実務レベルで完全に制御する方法を伝授する。

—

1. なぜ「型チェッカー」は柔軟性を許さないのか

まず、基本的な前提を共有しよう。`vec` は `vec` のサブタイプか? 直感的には「Yes」だが、型安全性の観点からは「No」だ。

もし `vec` を `vec` として扱えてしまうと、`vec` に `Cat` を追加する操作(`push`)が許容されてしまい、結果として `vec` の中に `Cat` が紛れ込む。これが、ジェネリクスにおける「型の不変性(Invariance)」がデフォルトである理由だ。

2. 読み込み専用の「共変(Covariance)」: `+T`

データを読み出すだけのコンテナであれば、サブタイプの代入を許可しても安全だ。これが「共変」である。Hackでは `+` を用いて、この特性を型システムに宣言する。

実践:読み取り専用コレクションの設計

<<__ConsistentConstruct>>
abstract class Animal {}
class Dog extends Animal { public function bark(): void { echo “Woof!”; } }

// +T を指定することで、このインターフェースは「読み取り専用」として扱われる
interface IReadOnlyCollection<+T> {
public function get(int $index): T;
}

class DogCollection implements IReadOnlyCollection {
public function get(int $index): Dog { return new Dog(); }
}

function processAnimals(IReadOnlyCollection $animals): void {
// +T のおかげで、DogCollection をここへ渡すことが可能になる
$animal = $animals.get(0);
}

// 利用例
$dogs = new DogCollection();
processAnimals($dogs); // 型チェックをパスする

知見: `+T` を付与した型パラメータは、メソッドの引数(入力)には使えない。メソッドの返り値(出力)にのみ使用可能だ。HHVMの型チェッカーは、この制約により「汚染」が起きないことを静的に証明している。

3. 書き込み専用の「反変(Contravariance)」: `-T`

逆に、データを受け取るだけの処理(Sink)においては、親クラスを受け取れる関数は子クラスも受け取れるはずだ。これが「反変」である。

実践:柔軟なログ出力ハンドラ

// -T を指定することで、Animal を扱える処理は Dog も扱えるようになる
interface IHandler<-T> {
public function handle(T $item): void;
}

class AnimalHandler implements IHandler {
public function handle(Animal $a): void { / 全ての動物を処理 / }
}

function execute(IHandler $handler): void {
$handler.handle(new Dog());
}

// 利用例
$animalHandler = new AnimalHandler();
execute($animalHandler); // 反変により、AnimalHandler は IHandler として振る舞える

知見: `-T` を付与した型パラメータは、引数(入力)にしか使えない。戻り値として生成することはできない。これは、APIのイベントリスナーやコールバック関数を設計する際に、コードの再利用性を劇的に向上させる。

—

4. プロダクションコードにおける設計の鉄則

実務において、複雑なジェネリクスを多用しすぎると、型チェッカーの推論コストが増大し、HHVMのビルド時間を圧迫する。以下の原則を守れ。

1. デフォルトは不変(Invariance)でよしとせよ: 明示的な `+` や `-` が不要なら、付けないのが最も堅牢だ。
2. インターフェースの分離: `IReadOnlyCollection` と `IWriteOnlyCollection` を分け、それぞれ `+T` と `-T` を適切に配置することで、複雑な継承関係を解決できる。
3. パフォーマンスへの意識: `+T` / `-T` は単なるコンパイル時のチェックであり、実行時のHHVMのオーバーヘッドはゼロだ。型安全性を高めるための「コストのかからない最適化」である。

最後に

Hackの型システムは、あなたのコードを縛る鎖ではない。それは、実行時に発生しうる「ありとあらゆるバグ」を、コードを書いているその瞬間に叩き潰すための最強の防壁だ。

共変・反変を理解することは、Hackの深淵に触れることと同義である。もしコードレビューで「なぜここで `+T` を使わないのか?」と問えるようになれば、あなたは既にプロジェクトのアーキテクトとして一段上の視座に立っている。

さあ、型システムを使い倒し、堅牢なプロダクトを設計せよ。

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