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

こんにちは!HHVMの内部構造やHackの厳格な型システムに魅せられた皆さん、日々のコーディング楽しんでいますか?

他のプログラミング言語、例えばPHPやJavaScriptあたりからHackの世界に飛び込んできたとき、最初に直面する壁であり、かつ最強の武器になるのが「厳格な静的型付け(Strict Mode)」と「不変性(Immutability)の強制」ですよね。

今回は、大規模アプリケーションでデータの一貫性を鉄壁の守りで保つための核心、`readonly`プロパティと不変コレクションによる副作用の排除について、HHVMの裏側の挙動も少し覗き見つつ、優しく、そして深く解説していきますよ。

ここをクリアすれば、あなたの書くHackコードの安全性と美しさは一段上のステージに到達します。一緒にバッチリマスターしていきましょう!

—

なぜ「副作用」は悪者扱いされるのか?

アプリケーションが複雑化していくと、「あれ? この変数の値、どこで書き換わったんだっけ…?」という悪夢のようなバグに遭遇したことはありませんか?

関数Aにデータを渡したら、知らないうちにそのデータの中身が書き換わっていて、関数Bで想定外の挙動を引き起こす……これが「副作用(Side Effect)」の正体です。

[データ] ──> 関数A (ここでこっそり書き換え!) ──> 関数B (バグ爆誕💥)

これを人力のコードレビューやテストだけで防ぐのは、ぶっちゃけ不可能です。だからこそ、言語の型チェッカーに「ここは絶対に書き換えちゃダメ!」と厳格に誓約させる必要があるのです。HackのStrict Modeと不変性機能は、まさにそのための最強の盾なんですよ。

—

1. `readonly` プロパティで「代入の自由」を剥奪する

Hackでは、クラスのプロパティに `readonly` 修飾子を付与することで、「初期化(コンストラクタでの代入)の時以外は、二度と値を書き換えられない」という制約を強制できます。

まずは基本的な使い方を見てみましょう。

<<__Strict>>
namespace HackMasterclass;

class UserProfile {
// readonlyプロパティの定義
// コンストラクタ以外からの代入は型チェッカーが即座に弾きます
public function __construct(
public readonly string $username,
public readonly string $email,
) {}
}

function update_settings(UserProfile $profile): void {
// 試しに書き換えてみようとする悪質なコード
// $profile->email = “hack_lover@example.com”; // <-- ここで型エラー! }

型チェッカーの慈悲なきエラー

もし、上記のコメントアウトを外してコンパイル(型チェック)しようものなら、HHVMの型チェッカーは容赦なく以下のようなエラーを叩きつけます。

> Can not assign to a readonly property (Typing[4110])

「おいおい、一度決めたメールアドレスを勝手に変えようとするなよ」と、コンパイラが未然にバグを防いでくれるわけです。最高に頼もしいですよね。

—

2. 不変コレクション(ImmVector / ImmMap)で中身の改竄を防ぐ

`readonly` はプロパティの「再代入」を防ぎますが、配列やコレクションの「中身の要素の追加・削除・変更」までは防げません。そこで登場するのが、Hackが誇る不変コレクション(Immutable Collections)です。

通常の `Vector` や `Map` は可変(Mutable)ですが、これらを `ImmVector` や `ImmMap` に置き換えることで、データ構造全体をガチガチに凍結できます。

<<__Strict>>
namespace HackMasterclass;

class Team {
// 不変ベクター型を使用
public function __construct(
public readonly string $teamName,
public readonly ImmVector $members,
) {}
}

function register_member(Team $team, string $newcomer): Team {
// $team->members->add($newcomer);
// ↑ ImmVectorには ‘add’ メソッド自体が存在しません!

// 代わりに、要素を追加した「新しいコレクションを持つ新しいインスタンス」を返す
// これこそが副作用のない関数型アプローチです
$updatedMembers =
TypeAssertion\s_some_collection_logic_here(); // 概念コード

return $team;
}

メモリ効率とHHVMの最適化

「毎回新しいコレクションを作っていたら、メモリやパフォーマンスに悪影響があるのでは?」と心配になるかもしれませんが、そこは世界最高峰のHHVM。不変データ構造の特性を活かし、内部のメモリ構造を効率的に共有(Structural Sharing)する仕組みが組み込まれています。安心してガンガン使ってください。

—

3. 実践:副作用を完全に排除した安全なデータフロー

それでは、ここまでの知識を組み合わせて、「ユーザーのスコアを更新する」というシチュエーションを例に、副作用のないクリーンな設計を書いてみましょう。

<<__Strict>>
namespace HackMasterclass;

// すべてが readonly な不変レコード型クラス
final class GameState {
public function __construct(
public readonly int $score,
public readonly ImmVector $achievements,
) {}
}

class ScoreManager {
// 状態を直接書き換えるのではなく、新しい状態を「生成して返す」
public static function addScore(GameState $current, int $points): GameState {
$newScore = $current->score + $points;

// 既存の不変ベクターに要素を追加した新しいImmVectorをスライスで作る
$newAchievements = ImmVector::fromItems(
ListUtils\concat($current->achievements, ImmVector[‘スコアタッパー’])
);

// 新しいインスタンスを返却(元の $current は1ミリも汚染されない)
return new GameState($newScore, $newAchievements);
}
}

このコードの美しいところは、`GameState` を受け取ったどの関数も、そのデータが途中で勝手に書き換えられている心配をする必要が一切ない点です。マルチスレッドや非同期処理が絡む複雑なコードベースであっても、競合状態(Race Condition)の恐怖から完全に解放されます。

—

まとめ:ここをクリアすればHackの基本はバッチリ!

  • `readonly` プロパティ: クラスの状態が後から勝手に書き換えられるのを防ぐ。
  • 不変コレクション(`ImmVector`, `ImmMap` など): コレクションの中身の改竄を防ぎ、データの安全性を担保する。
  • 副作用の排除: 値を書き換えるのではなく、「新しい値を返却する」関数型デザインを取り入れる。

この3つの原則を頭に叩き込んでおけば、Hackの厳格な型チェッカーはあなたの「厳しい上司」ではなく、最高の「相棒」になってくれます。

型があなたのコードを守り、コードがビジネスロジックを正確に表現する――この心地よさをぜひ実際の開発現場でも味わってみてくださいね。それでは、次のHackマスタークラスでお会いしましょう!

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