こんにちは!Hack言語の世界へようこそ。世界最高峰のHHVMと静的型システムを統括する立場から、今回はHackが誇る最強の武器の一つ、`readonly`属性による不変性の強制について深く掘り下げていきましょう。
他の言語からHackにやってきた開発者の中には、「オブジェクトを安全に扱いたいけれど、毎回ディープコピーするのはパフォーマンスが心配……」とか、「非同期処理やマルチスレッドで、意図しないデータの書き換え(副作用)に悩まされている」という方が少なくありません。
ここをクリアすれば、あなたの書くコードは一気に堅牢になり、HHVMの最適化エンジンをも味方につけた最高峰の関数型プログラミングが実践できるようになります。一緒にバッチリマスターしていきましょう!
—
1. なぜ「副作用」と「データの書き換え」は悪夢なのか?
大規模なアプリケーションや、非同期処理(Async)が飛び交う現代の開発現場において、最もデバッグが困難なバグの筆頭は「知らないうちにデータが書き換わっていた」という現象です。
[関数A] —> (データを渡す) —> [関数Bがこっそり中身を変更!]
|
+—> あれ?さっきとデータが変わってる!?(パニック)
従来のプログラミング言語では、これを防ぐために「オブジェクトの複製(クローン)」を多用してきました。しかし、それだとメモリ消費量が増え、HHVMのメモリ管理にとっても重い負荷になってしまいます。
そこでHackでは、「メモリ上に存在するデータをそのまま共有しつつ、型チェッカーのレベルで『書き換え禁止(Readonly)』を強制する」というアプローチをとりました。これが `readonly` 属性の本質です。
—
2. `readonly` の基本:読み取り専用の世界を作る
まずは、基本的な使い方を見てみましょう。Hackで `readonly` を使うには、ファイルの先頭で厳格モード(`<
<
namespace HackMasterclass;
// ユーザー情報を表すシンプルなクラス
class User {
// プロパティに readonly を付与
public function __construct(
public readonly string $name,
public readonly int $age,
) {}
}
function displayUser(readonly User $user): void {
// 型チェッカーが「読むこと」は許可するが、「書くこと」を静的に禁止する
echo “Name: ” . $user->name . “, Age: ” . $user->age . “\n”;
// もしここで以下のように書くと…
// $user->age = 30; // ❌ コンパイルエラー!
}
コードのポイント
- `readonly User $user` のように、引数の前にも `readonly` を置くことができます。これにより、その関数内へ渡されたオブジェクトが「読み取り専用」として扱われることを型システムが保証します。
- クラスのプロパティ自体にも `readonly` を付与することで、インスタンス化された後に値が書き換えられるのを完全にシャットアウトできます。
—
3. 「深い(Deep)不変性」の魔力と型チェッカーの挙動
Hackの `readonly` が優れているのは、単なる気休めではなく、オブジェクトのネスト構造(参照の連鎖)全体を静的に保護する点にあります。
イメージ図で見てみましょう。
[Readonly Container]
│
├─> [Readonly Profile]
│ │
│ └──> [Readonly Address] (すべて変更不可!安全!)
もし、`readonly` なオブジェクトの中に、通常のミュータブル(書き換え可能)なオブジェクトが混ざっていたらどうなるでしょうか? Hackの型チェッカーは、それを決して許しません。
<
namespace HackMasterclass;
class Address {
public function __construct(public string $city) {}
}
class Company {
public function __construct(
// ネストされたオブジェクト
public Address $address,
) {}
}
function processCompany(readonly Company $company): void {
// ❌ コンパイルエラー!
// readonly な Company の中にある Address は、
// 暗黙的に readonly として扱わなければならないため、
// ミュータブルなコンテキストへ漏れ出すことが禁止されます。
$innerAddress = $company->address;
}
「あれ? 中のプロパティを取り出したいだけなのにエラーになるの?」と思った方、素晴らしい着眼点です!ここがHackの厳格な型システムで最初にハマりやすいポイントです。
`readonly` なオブジェクトからプロパティを取り出す場合、その取り出した値(参照)もまた `readonly` として扱わなければなりません。
正しい書き方(`readonly` の伝播)
<
namespace HackMasterclass;
class Address {
public function __construct(public string $city) {}
}
class Company {
public function __construct(
public Address $address,
) {}
}
function processCompany(readonly Company $company): void {
// ✅ 正解:取り出す側も readonly を明示する
readonly $addr = $company->address;
echo $addr->city;
}
このように、`readonly` はドミノ倒しのように伝播します。一度 `readonly` の世界に入ったら、その中にある参照もすべて読み取り専用として扱う必要があるのです。これにより、意図せぬ副作用の混入を100%防ぐことができます。
—
4. 陥りやすい文法エラーと対策
開発現場でよくある失敗パターンをいくつかご紹介します。これらを避けるだけで、Hackマスターへ一歩近づけますよ。
エラー1: コンストラクタ以外での値の代入
`readonly` プロパティに値を代入できるのは、定義されたクラスのコンストラクタ内だけです。それ以外のメソッドから書き換えようとすると、型チェッカーが即座にレッドカードを出します。
class Counter {
public function __construct(public readonly int $count) {}
public function increment(): void {
// ❌ エラー!readonly プロパティはコンストラクタ外で変更できません
$this->count++;
}
}
対策: 状態を変化させたい場合は、新しいインスタンスを生成して返す(関数型言語でおなじみの “with-copy” パターン)を採用しましょう。
class Counter {
public function __construct(public readonly int $count) {}
public function increment(): Counter {
// ✅ 新しいインスタンスを返して不変性を保つ
return new Counter($this->count + 1);
}
}
—
5. まとめ:HHVMの最適化と未来
Hackの `readonly` 属性は、単なる「コードの安全ルール」にとどまりません。HHVM(HipHop Virtual Machine)の内部アーキテクチャにおいても、データが不変であることが保証されていると、コンパイラはアグレッシブな最適化(メモリレイアウトの効率化やロック競合の回避)を行うことができます。
- 安全な並行処理: 非同期処理(Async)が入り乱れても、データ競合(Data Race)の心配がゼロになる。
- 圧倒的な可読性: 関数シグネチャを見ただけで、「この関数はデータを破壊しない」と一目で分かる。
最初は型チェッカーの厳しさに少し驚くかもしれませんが、一度この規律に慣れると、バグの少ないクリーンなコードを書く快感に病みつきになるはずです。
ここをクリアしたあなたなら、どんなに複雑な非同期システムであっても余裕で乗りこなせますよ。明日からのHackライフを、さらに楽しんでいきましょう!