【実務・中級編】【中級者向け】不変データ構造の強制:`readonly`プロパティと不変コレクションによる副作用の排除 – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

Hackを掌握する極限の知見:`readonly`と不変コレクションで副作用を完全に焼き払う

コードレビューをしていて、いまだに「どこで書き換わったか分からないデータ」に怯えているとしたら、それは型チェッカーへの冒涜であり、HHVMの最適化エンジンに対する怠慢だ。

我々はPHPの泥沼から脱却し、Hackという極限まで研ぎ澄まされた静的型付き言語を選んだ。であれば、データの一貫性(Consistency)とスレッドセーフティの幻影を追い求めるのではなく、言語機構によって副作用を物理的に不可能にする設計を強制しなければならない。

今回は、Hackの厳格モード(`<<__Strict>>`)における `readonly` プロパティと不変コレクションを組み合わせ、バグの温床となる「状態変化」をコードベースから根絶する実践的アーキテクチャを解説する。

—

1. なぜ「ミュータブルなドメインモデル」は悪なのか

Webアプリケーションの複雑性は、大半が「予期せぬ状態変化(Mutation)」から生まれる。非同期API連携、並行処理、依存性injectionのコンテキスト間において、あるサービス層が渡したデータを別の層が知らぬ間に書き換える(いわゆる「エイリアシングの罠」)。

これを防ぐためにゲッターやセッターを書くのは、ボイラープレート(無駄な定型コード)を増やすだけの悪しき習慣だ。HHVMのJITコンパイラにとっても、オブジェクトの状態が常に変化する可能性を考慮せざるを得ない設計は、最適化の足を引っ張る。

Hackの `readonly` 修飾子と不変コレクション(`ImmMap`, `ImmVector` など)は、この問題に対する決定的な答えである。

—

2. 実践:完全不変なドメインコンポーネントの設計

以下のコードを見てほしい。これは、決済処理と非同期API連携を行うコンポーネントを想定した、プロダクション品質の厳格なHackコードだ。

<<__Strict>>

namespace Hack\Expert\Domain;

/

  • 決済トランザクションを表す不変値オブジェクト(Value Object)
  • 一度生成されたら、メモリ上で二度と書き換わることは許されない。

/
<<__KeyedBy("id")>>
final class TransactionContext {
// readonly修飾子により、初期化後の再代入を型チェッカーが厳格に禁止する
public function __construct(
public readonly string $id,
public readonly int $amountMicros,
public readonly string $currency,
// コレクション自体も不変(ImmMap)であることを強制する
public readonly ImmMap $metadata,
) {}

/

  • 状態を変更するのではなく、新しいインスタンスを返す(Witherパターンの極致)

/
public function withMetadata(ImmMap $newMetadata): this {
return new self(
$this->id,
$this->amountMicros,
$this->currency,
// 既存の不変マップと新しいマップを結合(効率的な構造共有が行われる)
ImmMap::fromItems(Dict\merge($this->metadata, $newMetadata)),
);
}
}

/

  • 副作用を排除した決済パイプライン

/
final class PaymentPipeline {

public async Task processAsync(
TransactionContext $tx,
): Awaitable {
// 非同期処理を模擬
await RescheduleWaitHandle::create(RescheduleWaitHandle::QUEUE_DEFAULT, 0);

// $tx の中身が途中で書き換わる心配は1ミリもない。
// なぜなら、型とメモリレイアウトレベルで保護されているからだ。

$enrichedMetadata = ImmMap\of(‘processed_by’ => ‘HHVM_JIT_WORKER_1’);

// 状態の「更新」ではなく「新しい世界の創出」を行う
return $tx->withMetadata($enrichedMetadata);
}
}

このコードの優位性

1. `public readonly` によるゼロコストの安全性: ランタイムでのプロパティガードは不要。Hackの型チェッカーがコンパイル時に不正な代入を検出し、HHVMはこれを最適化してダイレクトなメモリ読み込みとして処理する。
2. `ImmMap` による構造共有: Hackの不変コレクションは、変更を加えた際に全コピーを作成するのではなく、内部のツリー構造を共有(Structural Sharing)するため、メモリ消費とCPUコストが極めて低い。

—

3. パフォーマンス上の注意点:不変性の罠と回避策

「何でもかんでも不変にすればいいのか?」というと、そうではない。ここを間違えると、GC(ガベージコレクション)に余計な負荷をかけ、HHVMのパフォーマンスを殺すことになる。

❌ やってはいけないアンチパターン

ループの中で不変コレクションを毎回生成し直すこと。

// 【悪夢】O(N^2)に近いメモリ割り当てとGCの嵐を引き起こす
$collection = ImmVector::of(vec[]);
foreach ($hugeData as $item) {
// 毎回新しいImmVectorが構築され、古いものが捨てられる
$collection = $collection->add($item);
}

⭕ 正しいプロダクションパターン

構築時はミュータブル(可変)なコンテナを使い、境界を越える(公開する)瞬間レントゲン写真を撮るように不変化(Freeze)する。

// 【推奨】ビルダーパターン的なアプローチ
$builder = Vector::withCapacity(C\count($hugeData));
foreach ($hugeData as $item) {
// ローカルスコープ内でのミュータブルな操作は高速
$builder[] = $item;
}

// 境界をまたぐ瞬間に不変(ImmVector)に変換する
$safeCollection = $builder->toImmVector();

Hackのコレクション設計の美しさはここにある。「内部の計算は効率的なミュータブルで行い、APIの境界やドメインモデルのプロパティとしては不変(Immutable)を強制する」。このハイブリッド戦略こそ、シニアエンジニアが選択すべきアプローチだ。

—

4. コードレビューの視点:次世代のレビューアーへ

もしチームメンバーが以下のようなコードを書いていたら、即座に差し戻しを命じてほしい。

  • `public` プロパティに `readonly` がついておらず、サービスクラス間でオブジェクトが使い回されている。
  • 配列(`array` / `vec` / `dict`)をドメインモデルのプロパティとしてそのまま保持し、外部から書き換え可能になっている。

Hack言語の真価は、PHPの「動的で何でもありな世界」をスマートに模倣することではなく、厳格な静的解析とHHVMのアーキテクチャを信頼し、バグが入り込む余地を言語仕様によって物理的に消し去ることにある。

型チェッカーを味方につけ、副作用のない美しいコードベースを構築してほしい。それが、プロフェッショナルなHackエンジニアリングだ。

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