【入門編】Hackの『Readonly Collections』:不変性を保証するデータ構造の設計 – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

こんにちは!HHVMの内部構造やHackの静常時型システムの深淵を日々覗いているシニアエンジニアです。

他のモダン言語からHackの世界に飛び込んできた開発者の多くが、まずその圧倒的な型チェッカーの厳格さに驚かされますよね。中でも、大規模なWebアプリケーションの保守性を劇的に向上させる隠し味として、「Readonly Collections(読み取り専用コレクション)」という強力な武器が存在します。

今回は、このReadonly Collectionsがなぜ生まれ、どうやってあなたのコードをバグから守ってくれるのか、基礎から本質まで優しく、そして徹底的に解説していきますね。ここをクリアすれば、Hackのデータフロー設計の基本はバッチリマスターできますよ!

—

1. なぜ「不変性(Immutability)」がHackにおいて極めて重要なのか?

大規模なPHP/Hackアプリケーションを開発していると、こんな悪夢を経験したことはありませんか?

> 「あれ? この関数の内部でユーザーデータの配列をちょっと書き換えたつもりが、呼び元(Caller)のデータまで勝手に書き換わって(副作用)、別の場所で予期せぬバグが起きた……!」

変数を関数に渡したとき、知らず知らずのうちに中身を書き換えられてしまう「意図しない副作用(Side Effect)」は、バグの温床です。

従来のプログラミングでは、これを防ぐために「とりあえずディープコピー(複製)を作る」というアプローチが取られてきました。しかし、巨大なコレクションを毎回コピーしていては、メモリ効率やCPU性能の面で大きなコストがかかってしまいます。

ここで登場するのが、HHVMの型システムと深く統合された Readonly Collections です。

メモリをコピーせずに、変更だけを「型レベル」で禁止する

Readonly Collectionsの本質は、「実データはそのまま共有(ゼロコピー)しながら、型チェッカーの力で『書き込み操作』をコンパイル時に完全に遮断する」という点にあります。

イメージ図にしてみましょう。

[ 通常のコレクション ]
変数A (書き込み可) ──> [ データ本体 ] <── 変数B (書き込み可) ※Bの変更がAにも波及する危険! [ Readonly コレクション ] 変数A (書き込み可) ──> [ データ本体 ] <── readonly 変数B (読み取り専用) ※Bからは絶対に書き換え不可!安全! HHVMのランタイムにとっても、メモリ上のアロケーションを増やす必要がないため極めて高速でありながら、開発者は「絶対に中身が改変されない」という強い保証を手に入れられるのです。 ---

2. Readonly Collectionsの基本的な使い方

それでは、実際のHackのコードで書き方を見ていきましょう。
Hackでは、コレクション型の頭に `readonly` 修飾子を付与することで、そのインスタンスを読み取り専用として扱います。

以下のコード例を見てください。

<<__Strict>>
namespace Hack\Samples;

<<__EntryPoint>>
async function main_readonly_demo(): Awaitable {
// 通常のミュータブル(変更可能)なベクターを作成します
$names = Vector {‘Alice’, ‘Bob’, ‘Charlie’};

// 読み取り専用の参照として別の変数にバインドします
// これにより、この変数経由での要素の書き換えが静的型チェッカーによって禁止されます
readonly $readonlyNames = $names;

// 1. 読み取りは当然OKです
echo “最初のユーザー: ” . $readonlyNames[0] . “\n”;

// 2. 要素数を数えるのもOK
echo “合計ユーザー数: ” . count($readonlyNames) . “\n”;

// さあ、ここで書き換えを試みてみましょう(※次の章でエラーになる例を示します)
}

このように、既存のコレクションから `readonly` な参照を作り出すことができます。元の `$names` は変更可能なままですが、`$readonlyNames` を受け取った関数やスコープでは、安全に読み取りだけに専念できるようになります。

—

3. 陥りやすい文法エラーと型チェッカーの挙動

初心者の方が最もハマりやすいポイント、そしてHackの型チェッカーが優秀すぎるがゆえに遭遇するエラーを見ておきましょう。

次のようなコードを書いたとします。

<<__Strict>>
namespace Hack\Samples;

function update_user_list(readonly Vector $names): void {
// 【エラー!】readonlyなコレクションに対して要素の追加・変更はできません
// $names[] = ‘Dave’; // 型チェッカーが即座にコンパイルエラーを返します!

// 【エラー!】set メソッドももちろん拒否されます
// $names->set(0, ‘Zack’);
}

なぜこのエラーが出るのか?

Hackの型チェッカーは、`readonly` とマークされたコレクションオブジェクトに対して、`set()` や `[]=` といった「状態を変更するメソッド(Mutator methods)」の呼び出しを静的に検出し、ビルドを即座に失敗させます。

runtime(実行時)にエラーが起きて慌てるのではなく、コードを書いている瞬間にエディタ上でバグを潰せる。これがHackの厳格な静的型付け(Strict Mode)の最大の強みです。

—

4. 実戦で役立つ!副作用のない安全なデータフロー設計

実際の開発現場では、関数がコレクションを受け取るときに「この関数はデータを変更しませんよ(読み取り専用です)」という契約(Contract)をシグネチャに明示するために `readonly` を使います。

実用的なサンプルコードを組んでみましょう。

<<__Strict>>
namespace Hack\Samples;

// ログを出力するだけの安全な関数
// データの改変が行われないことが型シグネチャから一目でわかります
function print_audit_trail(readonly Vector $actions): void {
foreach ($actions as $index => $action) {
echo “[Audit] #{$index}: {$action}\n”;
}
}

<<__EntryPoint>>
function run_app(): void {
// アプリケーション層で生成されたアクション履歴
$auditLog = Vector {‘Login’, ‘ViewDashboard’, ‘UpdateProfile’};

// ログ出力関数に「読み取り専用」として渡す
print_audit_trail(readonly $auditLog);

// print_audit_trail の中で $auditLog が書き換えられていない安心感があるため、
// この後も安全に処理を続けられます
echo “処理が正常に完了しました。\n”;
}

ここがプロの知見:

他のプログラミング言語では「イミュータブルなコレクションを作るために専用のクラスを使う(例: ImmutableListなど)」というアプローチが一般的ですが、それだと既存のミュータブルなデータ構造と混ぜ合わせるのが面倒になりがちです。

しかし、Hackの Readonly Collections は、「一つの同じ実体(データ)」に対して、ある場所からは書き込み可能として扱い、別の場所からは `readonly` という型フィルターを通して安全に読み取り専用として扱うことができます。この柔軟性と厳格さのバランスが、HHVM上で動く大規模システム(Meta社内のコードベースなど)を極めて高い堅牢性に保っている秘密なのです。

—

まとめ

今回は、Hack言語における Readonly Collections の基本と、それがもたらす安全なデータフローについて解説しました。

  • Readonly Collectionsの本質: メモリを複製せず、型レベルで「書き込み操作」を禁止することで副作用を防ぐ。
  • メリット: 高速なパフォーマンス(ゼロコピー)と、コンパイル時の強固な安全性文脈の両立。
  • 注意点: `readonly` が付いた変数・引数経由では、要素の追加・変更メソッドが呼べない(型チェッカーが弾いてくれる)。

「変数がどこで書き換えられたか分からない」という悩みから解放されると、コードを書くのが一段と楽しく、そして自信を持てるようになりますよ。

日々のコーディングにぜひ取り入れて、ワンランク上のHackエンジニアを目指していきましょう!それではまた次回の知見でお会いしましょう!

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