こんにちは。HHVMの深淵を覗き込み、Hackという言語の血流そのものに触れてきた者として、今日は皆さんに少し「深い」話をしましょう。
Hackの厳格な型システム(Strict Mode)は、単なる「エラーを防ぐ網」ではありません。それは、コンパイル時にコードの正当性を証明するための強力な論理エンジンです。その中でも、多くの開発者が最初につまずく、しかし避けては通れない壁が「再帰型(Recursive Types)」です。
今日は、木構造のような再帰的なデータ構造を、Hackの型チェッカーを怒らせることなく、いかにエレガントに実装するかを紐解いていきましょう。
—
1. なぜ「再帰」は型チェッカーを迷わせるのか
まず、イメージしてください。例えば、以下のようなツリー構造を作りたいとします。
// ダメな例:これはHHVMの型チェッカーに拒絶されます
type Node = shape(‘value’ => int, ‘children’ => vec
なぜこれがダメなのか? それは、型チェッカーが `Node` を定義しようとした瞬間、その定義の中にまだ存在しない `Node` を見つけてしまうからです。再帰の深さが無限に続く可能性があるため、型チェッカーは「この型のサイズは一体どれくらいになるんだ?」とパニックを起こしてしまいます。
これを解消するために、Hackには「型エイリアスの再帰制限」というルールがあります。
—
2. 解決策:`newtype` と「型の中継地点」を作る
Hackでは、単純な `type` エイリアスで直接的な再帰を定義することはできません。しかし、`newtype` を使い、そこに「再帰の構造」を隠蔽することで、型チェッカーを納得させることができます。
具体的には、「再帰を許容するコンテナ」を挟むのが定石です。
<<__ConsistentConstruct>>
abstract final class Tree {
// 再帰を表現するために、型を一度「隠蔽」します
public static function create(int $val, vec
return new TreeImpl($val, $children);
}
}
// 内部実装クラスで再帰を許可する
final class TreeImpl extends Tree {
public function __construct(
public int $value,
public vec
) {}
}
なぜこれでうまくいくのか?
型チェッカーは「クラス」の定義であれば、内部で自身を参照しても「メモリ配置が確定している(ポインタとして扱える)」と判断します。`type` エイリアスで無理やり構造を展開しようとせず、「再帰する主体をクラスという境界の中に閉じ込める」。これが、メモリ管理を知り尽くしたアーキテクトが選ぶ安全な実装パターンです。
—
3. 陥りやすい罠:`vec` と `shape` の制約
よくある間違いとして、「JSONのような構造を `shape` で再帰的に定義したい」というケースがあります。
// これはコンパイルエラーになります
type JSONValue = shape(
‘type’ => string,
‘children’ => vec
);
Hackの `shape` は「固定されたキーと値のセット」です。サイズが可変になる再帰的な `shape` は、型推論の計算量を爆発させるため、厳格に禁止されています。
対策:
このような場合は、`shape` ではなく `enum` や `class` を組み合わせた「代数的データ型(ADT)」的なアプローチを採ってください。
// 推奨される設計:Discriminated Union的なアプローチ
abstract class JsonNode {}
final class JsonObject extends JsonNode {
public function __construct(public dict
}
final class JsonArray extends JsonNode {
public function __construct(public vec
}
このように「抽象クラス」を継承する形にすれば、型チェッカーは「`JsonNode` という型の中に、`JsonNode` の派生形が含まれる」という階層構造として理解できるため、安全に再帰を扱えるようになります。
—
4. 今日から使える「再帰マスター」への道
再帰的なデータ構造を扱う際のチェックリストをまとめました。
1. `type` エイリアスで再帰させない: 構造が単純なら `type` でいいですが、少しでも再帰が入るなら、迷わず `class` か `interface` を使いましょう。
2. `HH\MemberOf` や `vec` の型指定を厳格に: 再帰の末端でどんな型が来るのか、`mixed` に逃げずに具体的な型を当てるのが、Strict Modeの醍醐味です。
3. メモリ効率を意識する: HHVMは非常に高速ですが、再帰が深すぎるとヒープを圧迫します。末尾再帰最適化が効かないケースでは、イテレータパターンへの書き換えも検討してください。
最後に
Hackの型チェッカーが厳しいのは、皆さんのコードを「実行時にクラッシュさせない」という強い意志の表れです。再帰型の定義でエラーが出るのは、あなたのコードが悪いのではなく、「型チェッカーが安全性を保証できる境界線」に立っている証拠です。
その境界を、クラスという境界線でそっと補強してあげる。それができれば、皆さんはもうHackの初学者ではありません。HHVMのアーキテクチャを味方につけた、立派なHackエンジニアです。
分からないことがあれば、いつでもコードに聞いてみてください。型チェッカーは、最も正直で、最も頼りになるメンターですから。