副作用を型で殺す:HackのCoeffectsが導く「破壊不能な」アーキテクチャ
PHPという泥沼から脱出し、Hackを採用する最大の理由は「予測可能性」だ。しかし、単に型をつけるだけでは不十分だ。我々が真に手に入れるべきは、「どの関数が世界を書き換え、どの関数が安全に計算のみを行うか」をコンパイラに証明させる力である。
今回は、Hackの真髄であるCoeffects(コンテキスト)を活用し、副作用を型レベルで封じ込める設計術を伝授する。
—
1. なぜ「隠れた副作用」がシステムを殺すのか
PHPや従来のHackコードでよくある悲劇は、「純粋な計算関数だと思っていたら、内部でログを吐いたり、DBに書き込んだりしている」ケースだ。この曖昧さが、ユニットテストを困難にし、並行処理で競合を生む。
HackのCoeffectsシステムは、関数シグネチャに「この関数はどのような環境・能力を必要とするか」を明示させる。これを無視して副作用を呼び出せば、型チェックが即座に牙を剥く。これが、我々が望んだ「静的な安全」の正体だ。
—
2. 実践:Coeffectsで副作用を制御する
以下のコードを見てほしい。ここでは「純粋な計算」と「DB操作」を明確に分離している。
namespace App\Domain;
use HH\Capabilities;
/
- [defaults] は、この関数が何も特別な副作用(I/Oや状態変更)を
- 必要としない「純粋関数」であることを宣言する。
- もしこの中で print や DBアクセスをしようものなら、即座に型エラーだ。
/
function calculate_discount(float $price, float $rate): float [defaults] {
return $price (1 – $rate);
}
/
- [write_props] は、プロパティの書き込みや外部状態の変更を許可する。
- I/O操作が伴うサービス層のメソッドには、この制約を課すのが鉄則だ。
/
function update_user_balance(int $userId, float $amount): void [write_props] {
// DB操作などを想定
// [defaults] では書けない処理をここに隔離する
}
重要な設計パターン
- 計算ロジックは [defaults] に隔離せよ: ビジネスルールのコアは、必ず副作用のない関数に抽出する。これにより、テストコードでMockを一切使わずに「入力と出力」だけで検証が可能になる。
- 副作用の入り口を絞れ: `[write_props]` や `[io]` を持つ関数をできる限り末端(コントローラーやコマンドの直前)に押し込め。ドメインロジックが副作用能力を持っているなら、設計が腐っている証拠だ。
—
3. パフォーマンスへの影響とHHVMの最適化
「型チェックが増えると遅くなるのでは?」と懸念する諸君がいるかもしれない。だが、安心しろ。
HHVMにおいて、Coeffectsはコンパイル時の静的解析として処理される。実行時にはこれらのメタデータは型チェッカーのルールとして機能するだけで、実行時オーバーヘッドは皆無に近い。むしろ、副作用が型で分離されることで、HHVMのJITコンパイラは「この関数は純粋である」という情報を基に、強力な関数インライン化やレジスタ割り当ての最適化を自信を持って適用できる。
安全であることは、実は速いのである。
—
4. プロダクションコードでの応用法
実務で即座に役立つのは、非同期API連携の設計だ。外部通信を伴う処理は、必ず `[io]` 能力を要求するように定義する。
use HH\Capabilities;
// ネットワークI/Oを許可する関数
function fetch_exchange_rate(string $currency): float [io] {
// 外部APIコール
return 1.1;
}
// ユーザーのアクションを統合する
function process_payment(int $userId, float $price): void [io, write_props] {
// I/Oも状態変更も許可されたコンテキストでのみ、
// 依存する処理を呼び出すことが許される
$rate = fetch_exchange_rate(‘USD’);
update_user_balance($userId, $price $rate);
}
このコードの美しさは、`calculate_discount` を `process_payment` の中で呼んでも、`[defaults]` 側の制約が緩和されるわけではないという点にある。コンパイラは「純粋な関数が汚染された環境で呼ばれること」は許容するが、「副作用関数が純粋な環境で呼ばれること」は決して許さない。
—
チーフアーキテクトからの助言
君たちが書くコードは、単なるテキストではない。それはシステムという巨大な機械の「仕様書」だ。
1. デフォルトを信じるな: 最初は `[defaults]` をつけ、必要になった時だけ能力を解放しろ。
2. エラーメッセージを友とせよ: 副作用の型エラーが出たとき、それは「設計の綻び」を警告してくれている。それを回避するために `unsafe_` を使うな。ロジックを分離しろ。
3. 型は「ガードレール」ではない: 型は「物理法則」だ。その法則に従うコードを書けば、システムは勝手に堅牢になる。
HackのCoeffectsを使いこなせ。そうすれば、深夜の障害対応で「どこで状態が破壊されたのか」を追いかける無意味な作業から、君たちを永久に解放できるはずだ。