【実務・中級編】【上級者向け】HackのPhantom Typesによるビジネスロジックの型安全化:コンパイル時に状態遷移を保証する – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

型システムは「ドキュメンテーション」ではない。「契約」である。

Hackの`<<__Strict>>`モードを使いこなしているつもりでも、単に「値に型を付けているだけ」ならば、それはまだHackの真価を半分も引き出せていない。多くのエンジニアが犯す過ちは、状態遷移を`if`文や例外処理で制御しようとすることだ。

ビジネスロジックにおける「無効な状態遷移」は、本来コードを実行する前に、型チェッカーがその場で弾くべきものである。実行時に`InvalidStateException`を投げてクラッシュさせるのは、設計者の敗北に他ならない。

今日は、HackのPhantom Types(幽霊型)を用いて、状態遷移をコンパイル時に完全に静的保証するアーキテクチャを伝授する。

—

なぜ「状態」を型レベルで表現すべきなのか?

例えば、ECサイトの注文処理を想像してほしい。`Created` -> `Paid` -> `Shipped` という遷移があるとする。
もし、未払い状態の注文に対して `ship()` メソッドを呼ぶコードが書かれたらどうなるか?

// 悪い例: 実行時チェックに頼る
public function ship(): void {
if ($this->status !== Status::PAID) {
throw new Exception(“まだ支払われていません”);
}
// 処理…
}

これは脆弱だ。コードベースが肥大化すれば、誰かがこのチェックを書き忘れるリスクは100%発生する。
これを「型システムによる制約」へ昇華させるのがプロの仕事だ。

—

実装:Phantom Typesによる状態遷移の静的保証

Hackのジェネリクスと、インスタンス化不可能な「幽霊(Phantom)」となるクラスを組み合わせる。

<<__Strict>>
namespace App\Order;

// 状態を表すためのマーカー型(インスタンス化はしない)
interface State {}
interface Created extends State {}
interface Paid extends State {}
interface Shipped extends State {}

/

  • 注文クラス
  • @template TState as State

/
final class Order {
public function __construct(private int $orderId) {}

// 支払済み状態への遷移: Created -> Paid
public function markAsPaid(this $this): Order {
return new Order($this->orderId);
}

// 出荷処理: Paid -> Shipped
public function ship(this $this): Order {
return new Order($this->orderId);
}
}

このコードの何が「極限」なのか?

1. 呼び出し制限の強制: `markAsPaid` メソッドは `this` を要求する。つまり、`Order` や `Order` のインスタンスからこのメソッドを呼び出そうとすると、HHVMの型チェッカーがコンパイルエラーを吐く。
2. メモリ効率: `Order` インスタンス自体は単なるラッパーであり、実行時に複雑なステートマシンを保持する必要はない。型チェッカーが消えるランタイムにおいて、このPhantom Typeはゼロコストだ。
3. 安全性: `ship()` を呼べるのは `Order` だけ。バグの入り込む余地が構造的に存在しない。

—

実務で直面するパフォーマンスと設計の注意点

この設計を導入すると、必ず「オブジェクトの生成が増えるのでは?」という懸念が上がるだろう。

  • イミュータブルであることの価値: 上記コードでは状態遷移のたびに新しいインスタンスを生成している。しかし、HHVMのJITコンパイラは、これら短命なオブジェクトの生成と破棄を極めて効率的に最適化する。過度な心配は不要だ。むしろ、状態を可変(Mutable)にすることで発生する競合やバグのデバッグコストを考えれば、この設計は圧倒的に低コストである。
  • 型推論を汚さない: `this` という書き方はHack特有の強力なシンタックスだ。これにより、メソッドチェーンの途中で型が確定し、IDEの補完も完璧に機能する。

—

現場への導入:明日からできるステップアップ

もし既存のプロジェクトに導入するなら、まずは特定のドメインモデルの「重要かつ複雑な遷移」からこのパターンを適用することをお勧めする。

1. マーカーインターフェースを定義する: `State` を実装した空のインターフェースを用意する。
2. 既存クラスをジェネリクス化する: 既存のロジックを破壊せず、新しいクラスとして切り出すのが安全だ。
3. ファクトリで型を付与する: データベースからロードした直後は、適切な状態型を付与して返却する。

最後に

Hackの型システムは、単にバグを防ぐためのガードレールではない。「システムがどう振る舞うべきか」という設計思想を、コードそのものに刻み込むための言語だ。

「実行してみないとわからない」という不安から解放され、コンパイラと握手してデプロイする。この快感を知ったエンジニアこそが、真のHackマスターだと私は思う。

次回のレビューでは、`if ($status === …)` というコードを見かけたら、即座に「Phantom Typeで型レベルに引き上げろ」とコメントしてやってくれ。それが、チームのコードベースを最強にする唯一の道だ。

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