こんにちは!Hack言語の世界へようこそ。チーフアーキテクトの私です。
普段、他の言語(TypeScript、Go、PHPなど)からHackを触り始めると、「なんだこの厳格すぎる型チェッカーは……!」と驚かされることが多いですよね。特に ``Enum Class`(列挙クラス)とパターンマッチングを組み合わせて、「絶対にバグらせないステートマシン(状態遷移)」を実装する極意をお伝えします。ここをクリアすれば、あなたのHack力は一段とグッと引き上げられますよ!
—
1. なぜ従来のEnumや定数管理では不十分なのか?
Webアプリケーション開発では、「注文ステータス(未払い・発送済み・完了・キャンセル)」や「ユーザー権限」など、決まった状態を管理する場面がよくありますよね。
よくあるアンチパターンとして、ただの文字列や数値、あるいは従来の `enum` で管理してしまうケースがあります。
// 従来のやり方(ちょっと危険な香りがしますね…)
enum Status: string {
PENDING = ‘pending’;
SHIPPED = ‘shipped’;
COMPLETED = ‘completed’;
}
これの何が問題か分かりますか?
大規模なシステムになってくると、「どの状態からどの状態へ遷移していいのか」というルール(制約)がコードのあちこちに散らばり、誰も全体の遷移図を追えなくなってしまうのです。「発送済み」から一足飛びに「未払い」に戻るような不条理なバグが混入しても、静的型チェッカーは止めてくれません。
そこで登場するのが、Hackの `Enum Class` です。
—
2. `Enum Class` とは何か?(定数管理を超えた世界)
Hackの `Enum Class` は、単なる「値の集まり」ではありません。「型付きの定数のグループ」であり、それぞれの定数が独自の型(あるいは共通のベース型)を持つことができます。
まずは、今回の主役であるステートマシンを `Enum Class` で定義してみましょう。
「どの状態から、どの状態へ遷移できるか」を型レベルで縛り上げていきます。
—
3. 実践:型安全なステートマシンの実装
状態遷移のルールを次のように定めます。
- `PENDING`(未払い)からは、`SHIPPED`(発送済み)か `CANCELLED`(キャンセル)へ行ける。
- `SHIPPED`(発送済み)からは、`COMPLETED`(完了)へ行ける。
- `COMPLETED` と `CANCELLED` は終端状態(ここから先には進めない)。
このルールを、型チェッカーに完全に理解させましょう。
$initialState = OrderState::PENDING,
) {
$this->currentState = OrderState::valueOf($initialState);
}
// 状態を遷移させるメソッド
public function transition(
HH\EnumClass\Label
): void {
$nextState = OrderState::valueOf($nextStateLabel);
// 状態遷移のバリデーションをパターンマッチ風の分岐で行う
switch ($this->currentState) {
case OrderState::PENDING:
if ($nextState === OrderState::SHIPPED || $nextState === OrderState::CANCELLED) {
$this->currentState = $nextState;
return;
}
break;
case OrderState::SHIPPED:
if ($nextState === OrderState::COMPLETED) {
$this->currentState = $nextState;
return;
}
break;
case OrderState::COMPLETED:
case OrderState::CANCELLED:
// 終端状態からはどこにも遷移できない
break;
}
// 不正な遷移の場合は例外を投げる
throw new \InvalidArgumentException(
\Str\format(‘Invalid transition from %s to %s’, $this->currentState, $nextState)
);
}
public function getCurrentState(): string {
return $this->currentState;
}
}
💡 ここがポイント!
`HH\EnumClass\Label
これは、「Enum Class自体のインスタンスではなく、その中の特定のラベル(キー)を引数として安全に受け取るため」のHack特有のイディオムです。これにより、存在しない状態を関数に渡すミスを静的解析の段階で100%ブロックできます。
—
4. 陥りやすい文法エラーと型チェッカーの優しさ
Hackを書き始めた頃によくやってしまうミスを見ておきましょう。これを覚えておくだけで、コンパイルエラーと格闘する時間が劇的に減りますよ!
エラー例1: ラベルと値の型を混同する
// ❌ NG: valueOfを使わずに直接ラベルを比較しようとする
if ($this->currentState === OrderState::PENDING) { // これはOK(PENDINGはラベル名そのものとしても扱える文脈があるため)
}
// ただし、型安全に値を扱うには正しく `valueOf` を通す必要があります。
エラー例2: 存在しないEnum Classのメンバーを指定する
// ❌ NG: 定義されていない ‘REFUNDED’ を指定しようとする
$machine->transition(OrderState::REFUNDED);
// ➡️ 型チェッカーの叫び: “No such constant REFUNDED in enum class OrderState”
型チェッカーが「そんな定数、定義されてないよ!」とその場で教えてくれます。IDEの補完も完璧に効くため、タイポによるバグは文字通り消滅します。
—
まとめ:Hackの厳格さは、未来の自分への最高のプレゼント
今回は `Enum Class` を使った堅牢なステートマシンの実装方法について解説しました。
- 単なる文字列や定数ではなく、`Enum Class` を使うことでドメインの制約を型に落とし込める。
- `HH\EnumClass\Label` を活用して、不正な値の混入を静的型チェッカーに防いでもらう。
- 複雑な状態遷移も、意図が明確で安全なコードに仕上がる。
最初は型チェッカーの厳しさに少し窮屈さを感じるかもしれませんが、それはすべて「本番環境でシステムを絶対に落とさないため」のエンジニアへの優しさです。
ここをクリアしたあなたなら、もうHackの基本はバッチリマスターできていますよ!
さあ、自信を持って次のコードを書きに行きましょう!