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

Hackの構造的型付けと公称型:設計の「境界」を支配するアーキテクトの思考法

Hackのコードベースで最も頻繁に目にする「設計の迷い」がある。それは、「このデータ構造をShapeで受けるべきか、それともInterfaceを定義すべきか」という問いだ。

PHPのレガシーを引きずったエンジニアほど、すべてのデータ構造をクラスに閉じ込めようとする。あるいは、型定義をサボるために安易にShapeを多用し、後から「このShape、どこで定義されたんだ?」というデバッグ地獄に陥る。

今日は、HHVMの型チェッカー(HackC)の挙動と、メモリ消費、そしてコードの生存期間を見据えた「設計の極意」を授ける。

—

1. 構造的部分型(Shape) vs 公称的部分型(Interface)

まず、脳のOSを書き換えろ。

  • Shape (`shape(…)`): データそのもの。「何を持っているか(構造)」に価値がある。
  • Interface (`interface`): 振る舞い。「何ができるか(能力)」に価値がある。

この境界線を曖昧にすると、コードは腐敗する。

Shapeを選択すべき「静的」な瞬間

Shapeは、APIのレスポンスや、永続化層からのスナップショットのように「振る舞いを持たないデータ」を扱うためにある。これは構造的部分型であり、特定の型名に依存せずに型チェックが通るため、疎結合な境界で威力を発揮する。

Interfaceを選択すべき「動的」な瞬間

クラスインターフェースは、そのデータに対して「操作」が必要なときに使う。例えば、`format()`メソッドが必要だったり、実行時にポリモーフィズムが必要な場合は、絶対にShapeを使ってはならない。Shapeでこれを解決しようとすると、`match`式で型ガードを散りばめる羽目になり、拡張性が死ぬ。

—

2. 実務で「負債」を生むアンチパターン

多くのエンジニアが犯すミスは、「Shapeにロジックを詰め込む」ことだ。

// 悪い例:Shapeに対して無理やりロジックを結合させようとする
type TUser = shape(‘id’ => int, ‘name’ => string);

function getDisplayName(TUser $user): string {
// ロジックが散らばり、変更に弱くなる
return $user[‘name’] . ‘ (ID: ‘ . (string)$user[‘id’] . ‘)’;
}

このコードの問題点は、`TUser`というデータ構造が変わった瞬間に、プロジェクト中のあらゆる関数を修正しなければならないことだ。これを解決するのが、「値オブジェクト(Value Object)」としてのクラス設計である。

—

3. 生産性を最大化する「堅牢な設計」パターン

では、どう設計すればいいのか。私が推奨する黄金パターンは、「受信時はShape、処理時はドメインクラス」という変換レイヤーを設けることだ。

namespace App\Domain;

// 1. 外部境界(API連携等)では高速なShapeを利用
type TUserPayload = shape(‘id’ => int, ‘name’ => string);

// 2. 内部処理では、カプセル化されたクラスを利用
final class User {
public function __construct(
private int $id,
private string $name,
) {}

public function getFormattedName(): string {
return “{$this->name} (ID: {$this->id})”;
}

// 変換の責務をクラス自身に持たせる
public static function fromShape(TUserPayload $payload): this {
return new self($payload[‘id’], $payload[‘name’]);
}
}

なぜこの設計が最強なのか?

1. 境界の隔離: APIのスキーマが変わっても、`fromShape`メソッドを一箇所修正するだけで、ビジネスロジックへの影響をゼロにできる。
2. 型チェッカーの最適化: HHVMはクラス内のプロパティアクセスを高度に最適化する。Shapeのキーアクセスは連想配列のルックアップコストがかかるが、プロパティアクセスはインライン化の恩恵を受けやすい。
3. 可読性: `User::getFormattedName()`を呼ぶ方が、`$user[‘name’]`を直接操作するよりも意図が明確だ。

—

4. チーフアーキテクトからの「最後のアドバイス」

設計時に迷ったら、以下の「3秒判断」を使ってほしい。

  • そのデータにメソッドを生やしたいか?
  • Yes → `class` (interface) を定義せよ。
  • No → `shape` で十分だ。
  • そのデータは型名として再利用するか?
  • Yes → `newtype` や `type` エイリアスで名前をつけろ。
  • No → インラインで書け。

Hackは「厳格さ」を武器にする言語だ。動的言語のような柔軟性を求めるのではなく、「型がコードを語る」状態をいかに作るか。それが、HHVM上で最高のパフォーマンスと保守性を引き出す唯一のルートだ。

さあ、コードレビューに戻れ。次は、その「曖昧なShape」がクラスにリファクタリングされていることを期待している。

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