Hackの型チェッカーを「最速」に保つ:大規模モノレポにおける境界設計の哲学
Hackの型チェッカー(`hh_client`)は、単なる静的解析ツールではない。HHVMのJIT最適化と密接に連携し、プログラムの「意味論的整合性」を保証する要塞だ。しかし、コードベースが数百万行を超えるモノレポに達すると、型チェックは開発者の生産性を殺すボトルネックとなる。
多くの開発者が陥る罠は、「型定義が密結合しすぎている」ことだ。型システムは強力だが、不必要な依存関係は推論グラフを肥大化させ、再チェックの範囲を爆発的に増やす。
今日は、HHVMのアーキテクチャを知り尽くした視点から、型チェックを高速化し、かつ堅牢なシステムを構築するための「境界設計」について解説する。
—
1. 型チェッカーの「再帰」を断ち切る:モジュール境界の設計
Hackの型チェッカーは、依存関係の変更を検知すると、その影響範囲(Transitive Closure)を再計算する。この際、最もコストがかかるのは「複雑な型エイリアス」と「深い継承関係」だ。
アンチパターン:Fat Shared Models
すべてのドメインで共通の `User` クラスを使い回してはならない。変更が加わるたびに、システム全体の型解決が再走する。
推奨:インターフェースによる疎結合と「Type Opaque」
特定のモジュール内でのみ有効な型定義を行い、公開すべきデータ構造は `newtype` (opaque type) を活用して境界を隠蔽する。これにより、内部実装の変更が外部の型推論に波及するのを防ぐ。
—
2. 実践:保守性と速度を両立するコードパターン
以下の例は、ドメイン境界を明確にし、型チェックのコストを局所化するアーキテクチャの一例だ。
namespace App\Domain\User;
/
- 外部からは中身が見えない「Opaque Type」として公開
- 実装の詳細(内部構造)を変更しても、依存先は再チェックされない
/
newtype UserId = int;
interface UserProvider {
public function getById(UserId $id): UserEntity;
}
final class UserEntity {
public function __construct(
private UserId $id,
private string $name,
) {}
public function getId(): UserId {
return $this->id;
}
}
なぜこれが高速なのか?
1. 名前の隠蔽: `newtype` を使うことで、型チェッカーは「`UserId` が `int` であること」を境界の向こう側で無視できる。
2. 依存の断絶: `UserEntity` の内部構造(プロパティの追加など)をいじっても、`UserId` を扱っているだけの外部モジュールは型チェックの対象外(あるいはキャッシュヒット)となる。
—
3. 非同期API連携と「Shape」の賢い使いこなし
WebAPIとの境界では、`shape` を多用しがちだ。しかし、巨大な `shape` は型推論のメモリを食い尽くす。
パフォーマンスを意識したShape設計
APIレスポンスの型定義は、必ず最小限の単位に分割せよ。
// 悪い例:巨大なレスポンスを一括定義
type FullApiResponse = shape(
‘id’ => int,
‘meta’ => shape(…),
‘data’ => vec
// … 50行続く
);
// 良い例:ドメインごとに分割
type UserMeta = shape(‘created_at’ => int);
type UserData = shape(‘name’ => string);
type ApiResponse = shape(
‘id’ => int,
‘meta’ => UserMeta,
‘data’ => vec
);
チーフアーキテクトの知見:
型チェッカーは、構造体が大きくなればなるほど、フィールドの比較コストが増大する。再利用可能な小さな `shape` に分割することで、型情報のキャッシュ効率が飛躍的に向上する。
—
4. 現場で即効性のある「型チェック高速化」チェックリスト
開発の現場でチームの生産性を落とさないために、以下の規約を徹底してほしい。
- `type` vs `newtype`: 公開API境界では、必ず `newtype` を使い、実装の詳細を隠せ。
- 深すぎる継承の排除: 継承は型グラフを複雑にする。`trait` や `interface`、コンポジションを優先せよ。
- `hh_client` のログを監視せよ: `hh_client –log` を見れば、どのファイルが型チェックのボトルネックになっているか一目瞭然だ。型推論が重い箇所は、明示的な型注釈(Explicit Annotation)を入れろ。型チェッカーに「推論」させず「確認」させるだけで、速度は劇的に変わる。
最後に:型は「守るもの」ではなく「導くもの」
型定義は、単なるバグ除けのフェンスではない。コードの設計図そのものだ。境界を意識し、依存を整理することは、結果として「最も速い型チェック」という恩恵をもたらす。
大規模なモノレポにおいて、型チェッカーを味方につけるチームこそが、最速でプロダクトをデリバリーできる。コードを書くとき、常に問いかけてほしい。「この型定義は、システム全体の依存を増やしていないか?」と。
その問いこそが、Hackを使いこなす第一歩だ。