やあ。Hackの世界へようこそ。HHVMの深淵から戻ってきたばかりの君を歓迎するよ。
Hackの「Strict Mode」に触れていると、誰しも一度は「再帰的なデータ構造(Recursive Types)」という壁にぶつかるはずだ。ツリー構造や連結リストを定義しようとして、型チェッカーに冷たく突き放された経験はないかな?
「なぜ型を定義できないんだ? 自分の名前を型の中に含めているだけなのに!」と憤慨する君の気持ち、よく分かるよ。今日は、Hackがなぜその制限を設けているのか、そしてどうすれば安全かつエレガントに再帰構造を記述できるのかを、僕と一緒に紐解いていこう。
—
1. なぜ「型の中で自分自身を定義する」のは難しいのか?
まずは、君が遭遇したであろう「型エラー」を再現してみよう。例えば、単純な連結リスト(Linked List)を作ろうとしたとき、こんなコードを書かないかな?
// 悪い例:これはコンパイルエラーになる
type LinkedList = shape(
‘value’ => int,
‘next’ => ?LinkedList, // 型チェッカーが「自分自身を解決できない!」と悲鳴を上げる
);
なぜこれがダメなのか?
Hackの型チェッカー(`hh_client`)は、コードを解析する際、すべての型のサイズや構造を「有限の時間」で確定させようとする。もし、型が際限なく自分自身を包含していたら、それは無限の深さを持つ型になってしまうよね。型チェッカーが迷子にならないために、Hackでは「エイリアス(type)による直接的な再帰」を禁止しているんだ。
「じゃあ、再帰構造はどうやって作ればいいの?」という疑問が浮かぶよね。答えはシンプル。「クラス(class)」を使うこと。これこそが、Hackにおいて再帰を扱うための唯一にして最強の武器なんだ。
—
2. クラスによる再帰:Hackが好む「正しい」解決策
クラスは「名前(シンボル)」を持っているから、型チェッカーはそれを参照するだけでいい。中身がどうなっているかをその場で完全に展開する必要がないから、再帰的な定義が可能になるんだ。
実践:安全なツリー構造の定義
例えば、階層的なカテゴリツリーを作ってみよう。
<<__ConsistentConstruct>>
class Category {
public function __construct(
public string $name,
// クラス名自体を型として利用できる。これが再帰の鍵だ。
public vec
) {}
}
// 実際に使ってみる
function example(): void {
$root = new Category(‘Root’, vec[
new Category(‘Child A’),
new Category(‘Child B’, vec[
new Category(‘Grandchild B-1’),
]),
]);
echo $root->name; // ‘Root’
}
ここがポイント:
- クラス名は「ラベル」: `Category` という名前が型チェッカーにとっての目印になる。
- メモリの安全性: HHVMはクラスインスタンスをヒープ上に管理する。再帰的なポインタ構造も、このクラスベースのアプローチなら型チェッカーは「これはCategory型への参照である」と即座に理解できるんだ。
—
3. 「どうしてもType Aliasでやりたい!」という君へ:`newtype` との付き合い方
どうしてもデータ構造(shapeなど)として保持したい場合、`newtype` を使う方法もあるけれど、基本的にはクラスを推奨するよ。
ただ、特定の複雑な再帰構造を扱う場合、`Recursive types` を直接サポートする言語機能もある。しかし、Hackの哲学は「明示的で安全であること」。クラスを使った方が、メソッドによる振る舞い(ビジネスロジック)をカプセル化できるし、将来的な拡張性も段違いだ。
—
4. 初学者が陥りやすい「罠」と解決のヒント
再帰的な型を扱うとき、君が直面するであろうエラーを先回りして教えるね。
- 「Type alias is recursive」というエラー:
- 原因: `type MyType = shape(‘child’ => ?MyType);` のようなコード。
- 対策: `class` に書き換えよう。もしどうしても `shape` を使うなら、`mixed` を使って型を緩めるしかないが、それは「Strict Mode」の恩恵を捨てることになる。避けよう。
- NULLの扱い:
- 再帰の終了条件には必ず nullable な型(`?Category` など)を使うこと。これを忘れると、永遠に続くツリー構造(メモリリークの温床!)になってしまうから注意が必要だよ。
—
まとめ:Hackを掌握するために
今回学んだことを振り返ろう。
1. Hackの型チェッカーは、無限の展開を嫌う。 だからこそ、エイリアスの直接再帰は禁止されている。
2. クラスは再帰構造の救世主。 名前付きの型であるクラスは、再帰を「参照」として扱うことで安全性を確保している。
3. 「型」だけでなく「振る舞い」も一緒に。 再帰構造をクラスで定義すれば、その構造を操作するロジックも一緒に持たせられる。これがオブジェクト指向の醍醐味だよね。
再帰的なデータ構造をマスターすれば、君はもうHackの初級者から一歩脱却したことになる。次は、`Generic`(総称型)を組み合わせて、より型安全な再帰構造を作ってみてほしい。
分からないことがあれば、いつでもHHVMの深淵から顔を出すよ。君のHackライフが、型安全で実りあるものになることを願っている。Happy Hacking!