Hackの神髄:数百万行を瞬殺するIncremental Type Checkingの深淵
Hackにおいて「型チェックは重い」という常識は、もはや過去の遺物だ。数百万行に及ぶ巨大なコードベースで、なぜ私たちは数秒で型エラーを検知し、CIを回し続けられるのか。
今日は、HHVMの型チェッカー(`hh_server`)が裏側で何を行っているのか、そしてその仕組みを理解した上で、あなたがどうコードを設計すべきかについて話そう。
—
1. Incremental Type Checking:依存関係グラフの魔法
HHVMの型チェッカーは、単なる静的解析ツールではない。それは「依存関係の変更グラフ(Dependency Graph)」を動的に保持するインメモリデータベースだ。
なぜ速いのか?
1. Lazy Parsing & Partial Checking: すべてのファイルを毎回コンパイルすることはない。最後にチェックした状態からの「差分(Delta)」のみを追跡する。
2. Dependency Tracking: ある関数 `A` がクラス `B` のメソッド `m()` を呼んでいるとき、型チェッカーはその関係をグラフに保持する。`m()` のシグネチャが変わらない限り、`A` を再チェックする必要はない。
3. Shared Memory: `hh_server` は永続プロセスとして常駐し、抽象構文木(AST)と型情報をメモリ上にマッピングし続ける。これが、数秒でチェックが完了する物理的な理由だ。
—
2. 現場の設計:型チェッカーを「味方」につけるコーディング
型チェッカーのパフォーマンスと、コードの堅牢性はトレードオフではない。むしろ、型チェックを高速化する設計こそが、バグを排除し、保守性を高める唯一の解だ。
悪い設計:型依存の爆発
もし君が「あらゆる型を `mixed` で受け取り、実行時に `is` チェックで振り分ける」ようなコードを書いていたら、それは型チェッカーの依存グラフを破壊し、最悪のパフォーマンスを招く。
良い設計:直交性と厳格なインターフェース
型チェッカーが最も効率的に機能するのは、型境界が明確に分離されている時だ。
namespace App\Domain;
/
- 型チェッカーが依存関係を追跡しやすい「狭いインターフェース」の例
/
interface IPaymentGateway {
public function process(float $amount, string $currency): Awaitable
}
// 具象クラスはインターフェースに強く依存する
final class StripeGateway implements IPaymentGateway {
public async function process(float $amount, string $currency): Awaitable
// 厳格な型定義により、型チェッカーは推論を最小限の深さで停止できる
return true;
}
}
/
- 利用側も具象ではなくインターフェースに依存する。
- これにより、StripeGatewayの内部実装が変更されても、
- CheckoutServiceの型チェックは再走査をスキップできる。
/
final class CheckoutService {
public function __construct(private IPaymentGateway $gateway) {}
public async function execute(float $amount): Awaitable
$success = await $this->gateway->process($amount, ‘JPY’);
if (!$success) {
throw new \Exception(“Payment failed”);
}
}
}
—
3. パフォーマンスを落とす「アンチパターン」
以下の記述は、型チェッカーのオーバーヘッドを増大させる最悪の例だ。
- 過度な型推論への依存: `shape` や `tuple` を関数間で過剰に受け渡すと、型チェッカーは毎回その構造をマージ・検証する必要がある。複雑なデータ構造は必ず `newtype` や `class` で明示的に名前をつけろ。
- 深すぎるジェネリクス: `MyContainer
>>` のような深いネストは、型推論の探索範囲を指数関数的に増大させる。平坦な設計を心がけろ。 - `dynamic` 型の多用: `dynamic` は型チェックを無効化するが、型チェッカーにとっては「どこで何が起きるか不明」という最悪の依存関係を生む。これは技術的負債そのものだ。
—
4. チーフアーキテクトからの助言
数百万行のコードベースを維持する秘訣は、「型チェッカーが推論に迷わないコードを書くこと」にある。
もし君が書いているコードで型チェッカーのレスポンスが遅いと感じたら、それは「コードが悪い」のではなく、「型設計が曖昧である」という警告だ。
1. Strict Mode (HHVM 4.x+) を遵守せよ: `// strict` を入れないコードは、プロジェクト全体の型安全性を引き下げる。
2. 型エイリアスを活用せよ: 複雑な形状には必ず名前を。それはコンパイラへのヒントであり、未来の自分へのドキュメントだ。
3. 依存を局所化せよ: ファイルごとの依存関係を最小に保つことが、結果としてビルドパイプラインを最速にする。
Hackは、君の思考をコードという厳密な論理体系に変換するための、最も強力な武器だ。この武器を鈍らせないこと。常に厳格であり、常に論理的であれ。
それが、コードベースを10年先まで生き残らせる唯一の道だ。