Shapeか、Classか:Hackの型システムを掌握し、実行時オーバーヘッドを極限まで削ぎ落とす設計論
Hackの型システムは、単なる「バグを防ぐためのガードレール」ではない。HHVMという極めて洗練されたJITコンパイラ上で動作する以上、我々が選択する型は、メモリレイアウトに直結し、CPUのキャッシュヒット率を左右する「パフォーマンスの決定因子」となる。
今回は、多くの開発者が設計の谷間で迷う「Shape(構造的部分型)」と「Class(名前付き型)」の境界線について、ランタイムの深淵から切り込む。
—
1. 構造の「Shape」と、アイデンティティの「Class」
Hackにおける`shape()`は、単なる連想配列の強化版ではない。型チェッカー(HackC)は、コンパイル時にShapeの内部構造を厳密に検証する。しかし、実行時の実体は多くの場合、HHVMの`ArrayData`という動的なハッシュテーブル構造(もしくはその最適化版であるPacked/Mixed Array)として扱われる。
一方で、`class`は、HHVMの型システムにおいて明確な「型識別子」を持ち、プロパティのメモリレイアウトがクラス定義によって固定される(Class Property Storageの最適化)。
設計の分水嶺:いつどちらを使うべきか
- Shapeを使うべき時: 「純粋なデータ転送(DTO)」や「外部からのJSONペイロードの正規化」。構造が不変であり、かつ多相的な振る舞いを必要としない場合。
- Classを使うべき時: 「ドメインロジックの封入」や「状態遷移の管理」。メソッドを伴う振る舞いが必要な場合、あるいはインスタンスがアプリケーションのライフサイクルを通じて長期生存する場合。
—
2. ランタイムの裏側:メモリ最適化とJITの挙動
ここからが本題だ。HHVMがどのようにコードを機械語に落とし込んでいるかを知れば、設計の指針は自ずと決まる。
Shapeの限界:ハッシュルックアップのコスト
Shapeは柔軟だが、アクセスごとにハッシュテーブルのキー検索(あるいは、HHVMの最適化によるスロットアクセスの高速化)が発生する。膨大な数のShapeインスタンスを生成すると、メモリ上での断片化を招き、GC(ガベージコレクション)の圧力を増大させる。
Classの優位性:プロパティのメモリ配置
クラスインスタンスは、プロパティのオフセットがコンパイル時に確定する。これにより、JIT後の機械語は、特定のメモリ番地への直接アクセス(`mov`命令等)に置換される。
// 悪手:大量のShapeを保持する設計
// 100万件のデータでメモリ負荷とGC時間が激増する
type TUserRecord = shape(‘id’ => int, ‘name’ => string);
// 推奨:振る舞いを持つClass
// プロパティが固定され、HHVMのProp Storage最適化が効く
class User {
public function __construct(
public int $id,
public string $name,
) {}
}
—
3. 型チェッカーが教える「厳格さ」の真実
Strictモードにおいて、Shapeは「構造的」であるがゆえに、予期せぬ拡張性に注意が必要だ。`darray`のように扱われるShapeは、他の構造と互換性があるとみなされるリスクがある。
一方、`class`は「名目的な型(Nominal Typing)」である。クラス名が違えば、たとえ構造が完全に一致していても、型チェッカーは別物として扱う。これはセキュリティの要となる。
型の安全性を担保する設計テクニック
外部入力を受け取る際は`shape`で定義し、それを即座に`class`のコンストラクタへ渡してドメインオブジェクトへ昇華させる。この「境界線での変換」こそが、Hackで最も堅牢なアーキテクチャだ。
<<__EntryPoint>>
function main(): void {
// 1. 外部データはShapeで受け取る(柔軟性重視)
$rawData = shape(‘id’ => 1, ‘name’ => ‘Root’);
// 2. ドメインオブジェクトへ昇華(安全性の担保)
$user = new User($rawData[‘id’], $rawData[‘name’]);
// 3. 以降はClassのプロパティとして型安全にアクセス
echo $user->name;
}
—
4. チーフアーキテクトからの最終提言
「型をどう定義するか」は、「メモリをどう配置し、CPUをどう動かすか」と同義である。
1. データの寿命を見る: 短命なデータ(APIレスポンスのパース直後など)には`shape`を。長命で複雑なオブジェクトには必ず`class`を割り当てよ。
2. メソッドの有無が基準ではない: 「メソッドがあるからクラス」という考え方は古い。メモリの安定性と、コンパイル時の型一致によるセキュリティを考慮し、「ドメインの境界」を定義せよ。
3. 最適化を信じすぎない: HHVMは優秀だが、魔法ではない。構造体が巨大化しすぎれば、どの最適化エンジンも限界を迎える。疎結合な構造体は、常に小さく保て。
Hackという言語は、型システムという強固な武器を与えることで、我々に「システムの設計図そのものを最適化せよ」と問いかけている。この問いに対する答えを出すことこそが、シニアエンジニアの務めである。
次回の記事では、`HH\KeyedContainer`の深層と、ジェネリクスがもたらす再帰的な型推論のオーバーヘッドについて論じよう。準備はいいか。