【入門編】【中級者向け】`Enum Class`によるステートマシンの実装:定数管理を超えた網羅的チェックの活用 – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

こんにちは!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 $nextStateLabel,
): 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の基本はバッチリマスターできていますよ!
さあ、自信を持って次のコードを書きに行きましょう!

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