【実務・中級編】Hackの『Shapes』型における構造的部分型と名前付き型の境界線:設計時に迷わないための使い分け基準 – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

Hackの『Shapes』を掌握する:構造的部分型の罠とドメインモデリングの境界線

Hackの型システムを使いこなす上で、多くのエンジニアが「なんとなく」で使い分けているのが `shape` 型と `class`(名前付き型)だ。

「とりあえずデータ構造を定義したいから `shape` でいいか」――この判断が、数ヶ月後のコードベースに致命的な技術的負債を刻む。HHVMのチーフアーキテクトとして断言しよう。`shape` は「匿名データの搬送手段」であり、`class` は「ドメインの境界線を守る盾」だ。 この二つを混同した設計は、型チェックの恩恵を自らドブに捨てるに等しい。

今日は、Hackにおける構造的部分型(Shapes)と名前付き型の正しい使い分け、そして実務で「死なないコード」を書くための設計指針を授ける。

—

1. Shapesの本質:構造的という名の「諸刃の剣」

Hackの `shape` は構造的部分型(Structural Subtyping)に基づいている。これは「中身が同じ形をしていれば、それは同じものとみなす」という強力な柔軟性を提供する。

なぜこれが危険なのか?

APIのレスポンスやDTO(Data Transfer Object)の定義において、`shape` は便利だ。しかし、`shape` にビジネスロジックを寄せていくと、型チェッカーは「意図」を理解できなくなる。

// 悪い例:ドメインロジックがShapeに依存している
type UserProfile = shape(
‘id’ => int,
‘email’ => string,
);

function sendEmail(UserProfile $user): void {
// ここで渡されるのが「本当にUserなのか」を型チェッカーは厳格に検証しない
// 同じフィールドを持つ別のshapeが来ても通ってしまう
}

構造的部分型は「名前」ではなく「形状」で判定するため、似たような `shape` を定義した瞬間に、型システムによる保護境界が崩壊する。これを防ぐには、「外部との境界線」以外では `shape` を使わないという鉄則を守るべきだ。

—

2. 名前付き型によるドメインモデリング:堅牢さの正体

一方で、`class` や `record` を使った名前付き型は、型システムに「アイデンティティ」を与える。たとえ内部構造が同じ `(int, string)` であったとしても、`User` クラスと `Admin` クラスは別物として扱われる。

推奨される設計パターン:境界での変換(Anti-Corruption Layer)

外部APIから受け取った `shape` をそのままビジネスロジックに流し込んではいけない。アプリケーションの境界で、必ず「値オブジェクト」や「ドメインエンティティ」へ昇格させる。

// 境界線:ここまではShapeで受け取って良い
type RawUserResponse = shape(
‘id’ => int,
‘email’ => string,
);

// ドメインモデル:ロジックはここで行う
final class User {
public function __construct(
private int $id,
private string $email,
) {}

public static function fromShape(RawUserResponse $data): this {
// ここでバリデーションを完結させる
if ($data[‘id’] <= 0) throw new InvalidArgumentException("ID不正"); return new self($data['id'], $data['email']); } } この設計により、アプリケーションの深部では `User` オブジェクトだけを扱い、型安全性が完全に担保される。`shape` はあくまで「外部との通信プロトコル」としてのみ機能させるのだ。 ---

3. パフォーマンスとHHVMの最適化

HHVMにおいて、`shape` は配列の派生として最適化されているが、複雑な `shape` を多用すると、JITコンパイラの型推論負荷が増大する可能性がある。

  • `shape` のコピーコスト: `shape` は不変(immutable)の振る舞いをする際、内部的なコピーが発生することがある。数万件のデータを扱うバッチ処理では、`shape` ではなく `class` を用いて参照渡しを意識した設計にするほうが、メモリ効率とGCの負荷軽減において有利に働く。
  • 型チェッカーのオーバーヘッド: `shape` が複雑にネストすると、Hackの型チェッカーはユニフィケーション(型結合)に時間を費やす。型定義はなるべく浅く保て。

—

4. チーフアーキテクトからの提言:判断基準

システム設計時に迷ったら、以下の基準で即断せよ。

1. その型は「データ」か、「振る舞い」か?

  • データ(JSON、APIレスポンス、DBレコード)なら `shape` を検討してよい。
  • 振る舞い(メソッド、状態管理、バリデーション)が必要なら、迷わず `class` を書け。

2. その型は「公開」されるか?

  • パッケージの外へ公開するなら、型安全性を保証するために名前付き型(interface含む)を要求しろ。`shape` を型として公開するのは、インターフェースを定義する努力を放棄しているのと同じだ。

3. チームが「型」を誤認するリスクはあるか?

  • `shape` には型名が存在しないため、エラーメッセージが常に `’shape(…)’` と表示され、デバッグが困難になる。コードの意図を明確にするためにも、特定の意味を持つ構造体には必ず名前を付けろ。

結論

Hackの `shape` は「手軽な道具」だが、それは「使い捨ての道具」であることを忘れてはならない。長期的な保守性を求めるのであれば、型に名前を与え、ドメインの境界を定義する。 これこそが、大規模システムを破綻させない唯一の道だ。

コードは、ただ動けばいいのではない。「誰が読んでも型チェッカーの意図が伝わる」ように書くこと。それこそが、Hackを掌握するエンジニアの矜持だ。

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