【入門編】Hackの『Enum Class』と『Class Constants』の使い分け:型安全性の観点から – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

こんにちは!HHVMの内部構造やHackの静的型システムの底流に流れる哲学を愛してやまない、皆さんの先輩エンジニアです。

他の言語(TypeScript、Java、PHPなど)からHackの世界へ飛び込んだとき、最初に心を奪われるのは、何と言ってもあの妥協なき静的型チェックですよね。「Strict Mode(厳格モード)」がもたらす圧倒的な安全性は、大規模なコードベースをまるで滑走路のようにスムーズに飛行させてくれます。

さて、今回はそのHackの型システムにおいて、データの「分類」や「定数管理」をするときに必ずぶつかる永遠のテーマ、「Enum(列挙型)」と「Enum Class(列挙型クラス)」の使い分けについて、型安全性の極限の観点から徹底的に解説していきますね。

ここをクリアすれば、Hackの型システムの本質がぐっと見えてきますよ。さあ、一緒に深掘りしていきましょう!

—

1. そもそもEnumとEnum Classって何が違うの?

まずは、それぞれの基本的な姿かたちを確認しておきましょう。

従来の `enum` は、値をグループ化するためのシンプルで便利な仕組みです。一方、Hackの `enum class` は、一歩進んで「型と値の結びつき(Genericsや型制約)を伴う、極めて強力なメタプログラミングの武器」です。

頭の中で次のようなイメージ図を描いてみてください。

[ 従来の Enum ]
WalletStatus
├── PENDING (int)
├── ACTIVE (int)
└── CLOSED (int)
※ 値の型は一律。個別の定数に異なるデータ型を持たせることはできない。

[ Enum Class ]
Currency (Enum Class)
├── USD => float
├── JPY => int
└── CODE => string
※ 各定数ごとに「紐づく値の型(T)」を厳密に定義できる!

この「定数ごとに異なる型を安全に保持できるか否か」が、両者を分かつ最大の境界線になります。

—

2. 基本的な使い方とコードの読み方

百聞は一見に如かず。実際のHackのコードを見ていきましょう。どちらも `<<__STRICT__>>`(厳格モード)で記述するのがHackの作法です。

従来の Enum の世界

まずは、お馴染みのシンプルなEnumです。ステータス管理など、値の型が均一で十分なケースで活躍します。

<<__STRICT__>>

namespace HackMaster\Examples;

// 従来のEnum: 値の型は int で統一
enum UserStatus: int {
PENDING = 0;
ACTIVE = 1;
BANNED = 2;
}

class UserHandler {
public function handleStatus(UserStatus $status): string {
// 型チェッカーが全てのケースを網羅しているか(Exhaustiveness)を検知してくれます
switch ($status) {
case UserStatus::PENDING:
return ‘承認待ちです’;
case UserStatus::ACTIVE:
return ‘アクティブユーザーです’;
case UserStatus::BANNED:
return ‘BANされています’;
}
}
}

次世代の Enum Class の世界

続いて、Enum Classの登場です。ここでは、各定数がそれぞれ「独自の型」を持つことができます。型安全性のレベルが文字通り一段階跳ね上がります。

<<__STRICT__>>

namespace HackMaster\Examples;

// Enum Classの定義: 各定数が型(Type)を持つ
enum class SettingKey: mixed {
int TIMEOUT_SECONDS = 42; // この定数の型は int
string API_ENDPOINT = ‘https…’; // この定数の型は string
bool IS_DEBUG_MODE = true; // この定数の型は bool
}

class SettingReader {
// Enum Classの特定のキーを受け取り、そのキーに対応する正確な型を返すジェネリックな関数
public static function getSetting(
// SettingKey の中で、型 T を持つ定数だけを指すことができる型指定
HH\EnumClass\Label $label
): T {
// 実際の値を取り出す処理(HHVMの内部では最適化されています)
return SettingKey::valueOf($label);
}
}

この `SettingReader::getSetting()` のシグネチャを見てください!引数に渡したラベル(例:`SettingKey::TIMEOUT_SECONDS`)が `int` 型であるならば、返り値も自動的に `int` として型チェッカーに認識されます。呼び出し側でキャストや面倒な型アサーションを行う必要は一切ありません。これがEnum Classの真骨頂です。

—

3. 陥りやすい文法エラーと型チェッカーからの教訓

Hackの型チェッカーは非常に優秀ですが、それゆえに最初は戸惑うエラーに直面することもあります。よくある罠を見てみましょう。

罠1:従来のEnumで異なる型を混ぜようとする

// 【やってはいけない例】
enum BadEnum: int {
ID = 1;
// エラー: int 型と宣言しているのに string を入れようとしたため、型チェッカーが即座に弾きます
// NAME = “Alice”;
}

【エラーの背景】
従来のEnumは「単一の基底型(underlying type)」しか持てません。もし異なる型のデータを一つの集合として扱いたい場合は、従来のEnumではなく、まさにEnum Classの出番となります。

罠2:Enum Classの型制約(Label)を無視する

// 【やってはいけない例】
function updateTimeout(HH\EnumClass\Label $label): void {
// …
}

// 呼び出し側
// updateTimeout(SettingKey::TIMEOUT_SECONDS);
// -> 型チェッカーエラー:
// Expected: Label
// Actual: Label (TIMEOUT_SECONDSの中身はintのため)

【ここがポイント】
「設定キーなんだからどれも一緒でしょ」と雑に扱おうものなら、Hackの厳格な型チェッカーが「型が一致しません」と優しく、しかし毅然と教えてくれます。このお陰で、設定値の読み出しミスや型の勘違いによるバグを、実行時(Runtime)を迎えるはるか前、エディタ上(Compile time)で100%根絶できるのです。

—

4. どっちを採用すべき?判断基準のマスターガイド

「では、実際の開発ではどちらを使えばいいの?」という疑問に、フルスタックエンジニアとしての実践的な判断基準をお答えします。

以下のチェックリストを参考にしてください。

✅ 従来の `enum` を採用すべきケース

1. 値の型がすべて同じ(例:すべて `int` やすべて `string`)。
2. 単純なステータスコード、曜日、権限レベルなど、有限な選択肢の列挙である。
3. `switch` 文での網羅性チェック(Exhaustive switch)の恩恵をシンプルに受けたい。

✅ 新しい `enum class` を採用すべきケース

1. 定数ごとに保持すべきデータの型がバラバラである(プラグインのメタデータ、設定値、型安全なDIコンテナのキーなど)。
2. 「キーと値のペア」において、キーを指定した瞬間に返り値の型が自動的に一意に決まってほしい(高度な型推論をフル活用したい)。
3. 実行時のオーバーヘッドを抑えつつ、高度なメタプログラミング的アプローチを静的型安全の枠内で完結させたい。

—

まとめ

HackにおけるEnumとEnum Classの選択は、単なる「書き方の違い」ではありません。それは、アプリケーションの設計思想において、どこまで型安全性を厳密に担保するかというエンジニアリングの決断そのものです。

  • シンプルな列挙には、王道の `enum` ですっきりと。
  • 型と値が複雑に絡み合う高度なドメインモデルには、次世代の `enum class` で鉄壁の型防御を。

ここをクリアできれば、あなたのHackコードは、HHVMのパフォーマンスを極限まで引き出す、美しく頑健なアーキテクチャへと生まれ変わります。

分からないことがあればいつでも先輩に聞いてくださいね。それでは、素晴らしいHackライフを!

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