Enum / Sum Type
Last Updated: 2026-09-28 02:00
結論
melangでは、enumを整数定数へ寄せず、独立した型として持つ。variantは単なる整数・文字列定数ではなく、その enum 型の値そのものである。フィールド構成を持ちうる型に近い存在であるため、melangの型命名規則(型はPascalCase、変数・関数はlowerCamelCase)に合わせ、variant名もPascalCaseで始める。
enum Status {
Pending
Running
Completed
}
enumと整数・文字列の暗黙変換は禁止する。外部表現との変換は明示的に行い、不正な値から enum を生成する処理はResultを返す。
matchは網羅的であることを要求する
enumに対するmatchは、全variantを網羅することをコンパイル時に要求する。
match status {
Status.Pending => "pending"
Status.Running => "running"
Status.Completed => "completed"
}
variantを追加したとき、既存のmatchに不足があればコンパイルエラーとして検出できることを重視する。実行するまで気づけない分岐漏れをなくすのが狙いである。
Rust方式のenum / ADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。を基本モデルにする
単純な列挙ではなく、Rustのenumを基本モデルとして採用する。各variantはそれぞれ異なる型・異なるフィールド構成を持てる。
enum Result {
Success {
user: User
}
ValidationError {
errors: List<String>
}
NetworkError {
status: Int
message: String
}
}
variantはフィールドなし・単一値・複数の名前付きフィールドを持てる設計とする。Option / Resultもこの仕組みで表現できる形を基本とする。Java/Kotlinの sealed class/interface のように「別々に定義された型を閉じた継承階層にする」機能を追加するかは別論点とし、まずはRust型enum / ADTで必要なモデリングを賄う。
Boolean も閉じた集合として扱う
Booleanは言語上の基本型として提供するが、意味論としてはtrue / falseの閉じた2値集合として扱う。matchではenumと同様に網羅性を検査できる。
任意の値へのmatchとの違い
enum / Booleanのmatchは値集合が閉じているため、全ケースを列挙できればそのまま値を返せる。一方、整数・文字列など任意の値へのmatchは値集合を網羅できない。melangでは値matchを許可する場合、未一致を型として扱えるよう結果をResultとする方針とする。
このnoteで決めないこと
- enum / variantの具体的なsyntax。
- generic enumのsyntax。
- variantへのfield名を許可するか。
- discriminant値をユーザーが指定できるか。
- JSON / DB / OpenAPI等へのserialization表現。
- unknown value / forward compatibility。
- matchの網羅性エラーとwildcardの扱い。
他言語比較
enum / Sum Typeの設計は、大きく3つに分かれる。
- enum専用の型機構を持たない: 関連する整数定数を定義する慣習に寄せる(Go・C++・C#)。payload付きのADTを表現するには、別の仕組み(
std::variant、継承階層など)を自前で組む必要がある。 - 単純な列挙とADTを別機構で用意する:
enumは整数・状態の列挙に、payload付きのADTは別の機能(sealed class/interface、literal union)に役割を分ける(Java・Kotlin・TypeScript・Python・PHP)。 - enum自体がpayload付きのADTとして設計されている: variantごとに異なるフィールドを持て、matchの網羅性チェックもコンパイラが担う(Rust・Swift・Scala・MoonBit)。Zigは
enum単体ではpayloadを持てないが、enumをタグとするunionを組み合わせたunion(enum)というtagged unionで同様の表現を実現する。専用のenum型はなくても、data/ variant type + pattern matchingで同じことを実現する言語もある(Haskell・OCaml・Elixir)。
| 言語 | enum / 相当機能 | payload付きvariant | 網羅性チェック | 説明 |
|---|---|---|---|---|
| Rust | enum | ○ | ○ | enum自体がpayload付きのdiscriminated union。 enum自体がdiscriminated unionであり、各variantが異なるフィールド構成を持てる。matchは全variantの網羅をコンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。が要求し、variant追加時に既存matchの不足を検出できる。melangが目指すモデルに最も近い。 |
| MoonBit | enum | ○ | ○ | constructorがpayloadを持てるADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。型としてのenumを持つ。 constructorがpayloadを持てるADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。型としての`enum`を持ち、`match`は全constructorの網羅をコンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。が要求する。RustやOCamlに近い設計で、melangが目指すモデルに近い。 |
| Swift | enum(associated value) | ○ | ○ | enumがassociated valueを持ち、payload付きvariantを表現できる。 `enum`自体がassociated valueを持てるため、variantごとに異なるデータを保持できる。`switch`は全caseの網羅をコンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。が要求し、melangが目指す設計に近い候補。 |
| Kotlin | enum class / sealed class | sealedで○ | ○ | 単純な列挙とADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。を、enum classとsealed classで役割分担する。 単純な列挙は`enum class`、payload付きのADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。は`sealed class` / `sealed interface`と役割を分ける。`when`式はsealed階層を網羅しないとコンパイルエラーになり、enumのwhenでも同様に網羅性を検査する。 |
| Java | enum / sealed interface | 限定的 | 条件付き | enumはクラス的な機能を持つが、ADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。はsealed + recordで表現する。 `enum`はクラスとして状態・メソッドを持てるが、variantごとに異なるフィールド構成を持つADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。ではない。ADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。用途は`sealed interface` + `record`で表現し、switch expressionと組み合わせれば網羅性を検査できる。 |
| Scala | enum / sealed ADT | ○ | ○(条件あり) | enumはpayload付きのADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。表現にも使える。 Scala 3の`enum`はvariantごとに異なるフィールドを持てるADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。表現に使え、sealedな階層への`match`は網羅性チェックの対象になる。ただし設定によっては警告どまりになる場合もある。 |
| C# | enum(整数型) | × | 基本なし | enumはunderlying integral typeを持つ整数寄りの列挙型。 `enum`はunderlying integral typeを持つ列挙型で、payloadを持てない。ADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。表現には`record`の継承階層やパターンマッチを組み合わせる必要があり、`switch`の網羅性チェックは基本的にない。 |
| C++ | enum / enum class | × | 基本なし | enumは整数値の列挙にとどまり、Sum Typeは別に組む。 `enum` / `enum class`は整数値の列挙にとどまり、variantごとに異なるデータを持てない。payload付きの合算型は`std::variant`やタグ付き構造体を自前で組む必要があり、`switch`の網羅性チェックも基本的にない(コンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。の警告に頼る程度)。 |
| C | enum(整数定数) | × | × | enumは名前付きの整数定数にすぎず、payloadも網羅性チェックもない。 `enum`はコンパイル時に整数値へ展開されるだけの名前付き定数の集まりで、型としての区別も弱く(実体は`int`と相互変換できる)、variantごとに異なるデータを持たせることもできない。payload付きの合算型を表現したいときは、タグ用の`enum`と`union`を自前で組み合わせた「tagged union」を手で書く必要があり、タグと`union`の対応が壊れていてもコンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。は検出しない。この手組みのtagged unionが、C++の`enum`+`std::variant`を経て、Rust・SwiftのenumがVariantとpayloadを安全な形で一体化するまでの出発点になっている。`switch`の網羅性チェックは標準では行われず、`-Wswitch`のようなコンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。拡張の警告に頼るしかない。 |
| Go | 組み込みenumなし(named type + const) | × | × | 組み込みのenumがなく、named type + constで代替する。 言語組み込みのenumがなく、named type + `const` / `iota`で整数定数を列挙する方式を取る。Go公式Wikiも「Go does not support enums」と明記している。`switch`に網羅性チェックはなく、variant追加時の対応漏れをコンパイル時に検出できない。 |
| Zig | union(enum)(tagged union) | ○ | ○ | enumとunionを組み合わせたunion(enum)でtagged unionを表現する。 `enum`は整数値ベースの単純な列挙、`union`はvariantごとに異なる型を持てるが現在どのフィールドが有効かを実行時に保持しない生のunion。両者を組み合わせた`union(enum)`が、有効なvariantをタグとして安全に追跡できるtagged unionで、Rustの`enum`と完全に同じ構文ではないが同等のADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。/Sum Typeを表現できる。`switch`は`union(enum)`の全variantを網羅しないとコンパイルエラーになる(`else`を書けば任意の網羅で許容される)。 |
| OCaml | variant type | ○ | ○(警告) | variant typeが標準機能としてpayloadを持てる。 variant typeが標準機能で、コンストラクタごとに異なるデータを持てる。パターンマッチの網羅漏れはデフォルトで警告になり、`option`型もこの仕組みで表現される。 |
| F# | 判別共用体(Discriminated Union) | ○ | ○(警告) | 判別共用体取りうる形が閉じた集合になっている型を、caseごとに異なるデータ(payload)を持たせて表現する仕組み。代数的データ型(ADT)のSum Typeに相当し、F#やOCaml(variant type)で使われる呼び方。パターンマッチと組み合わせて使うことで、全caseの網羅をコンパイラに検証させられる。が標準機能としてpayloadを持ち、melangが目指すモデルに近い。 OCaml由来の判別共用体取りうる形が閉じた集合になっている型を、caseごとに異なるデータ(payload)を持たせて表現する仕組み。代数的データ型(ADT)のSum Typeに相当し、F#やOCaml(variant type)で使われる呼び方。パターンマッチと組み合わせて使うことで、全caseの網羅をコンパイラに検証させられる。(Discriminated Union)が標準機能で、caseごとに異なるデータを持てる。パターンマッチの網羅漏れはデフォルトで警告になり、`Option<T>`もこの仕組みで表現される。「不正な状態を表現できなくする」設計をそのまま実現できる型として、書籍『Domain Modeling Made Functional』でも中心的に扱われる。 |
| Haskell | data | ○ | compiler warning等 | dataによる代数的データ型複数の型を組み合わせて新しい型を作る手法。「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼び、Rustの`enum`やHaskellの`data`が代表例。各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。が中心。 `data`によるADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。が中心で、各コンストラクタが異なるフィールドを持てる。網羅性はコンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。の警告(`-Wincomplete-patterns`)として検出でき、警告をエラー扱いにするビルド設定も広く使われる。 |
| Erlang | 専用enum型なし(tagged tuple) | ○ | pattern matching中心 | 専用のenum型はなく、tagged tupleとpattern matchingで表現する。Elixirの元になった設計。 専用のenum型はなく、atomとtagged tuple(`{success, User}`のような組)によるpattern matchingでADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。的に表現する。網羅性はコンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。ではなく、実行時のマッチ失敗(`function_clause`エラー)として現れ、プロセスを異常終了させて上位のsupervisorに委ねる「let it crash」の一部として扱われる。Elixirの`{:ok, value}` / `{:error, reason}`という慣習はこの構文をそのまま受け継いでいる。 |
| Elixir | 専用enum型なし(tagged tuple) | ○ | pattern matching中心 | 専用のenum型はなく、tagged tupleとpattern matchingで表現する。 専用のenum型はなく、atomとtagged tupleによるpattern matchingでADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。的に表現する。網羅性はコンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。ではなく、実行時のマッチ失敗(`FunctionClauseError`)として現れる。 |
| TypeScript | enum / union type | unionで○ | 強制なし(neverで検証可) | enumよりも、literal unionでdiscriminated unionを表現する。 `enum`はGoに近い整数/文字列定数寄りで、payload付きのADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。表現にはliteral union(discriminated union)を使う。`switch`自体に網羅性チェックはないが、`never`型への代入という慣用句で擬似的に検証できる。 |
| JavaScript | 組み込みenumなし | × | × | 組み込みのenumがなく、慣用的にオブジェクトで代替する。 組み込みのenumがなく、タグ付きのプレーンオブジェクト等で慣用的に代替する。網羅性を検査する仕組みは言語自体にはない。 |
| PHP | enum(backed enum) | × | matchは実行時エラー | backed enumで値を持てるが、matchの網羅漏れは実行時エラー。 backed enumで文字列/整数値を持てるが、variantごとに異なるフィールドは持てない。`match`式は全caseを網羅しないと、コンパイル時ではなく実行時に`UnhandledMatchError`になる。 |
| Ruby | 組み込みenumなし | × | × | 組み込みのenumがなく、symbolや定数で代替する。 組み込みのenumがなく、symbolや定数で列挙値を代替するのが慣用。網羅性を検査する仕組みはなく、書き忘れは実行時にしか気づけない。 |
| Python | enum.Enum | 基本× | 基本なし | enum.Enumはpayloadを持たず、網羅性チェックも基本ない。 標準ライブラリの`enum.Enum`はpayloadを持たず、`match`文自体に網羅性チェックは基本的にない。型チェッカ(`mypy`等)で`assert_never`などを使えば擬似的に検証できる。 |
比較の観点は次の通りである。
- 言語組み込みのenum(相当機能)を持つか。
- variantごとに異なるフィールド構成(payload)を持てるか。
- matchや
switchが全variantを網羅しているかを、コンパイラが検査するか。
Go・C++・C#は、enum専用の型機構を持たないか、整数値の列挙にとどまる。Goは言語組み込みのenumがなく、named type + const / iotaで関連する整数定数を定義する。Go公式Wikiも「Go does not support enums」と明記している。C++のenum / enum classとC#のenumはunderlying integral typeを持つ列挙型で、payloadを持てない。いずれもswitchに網羅性チェックはなく、variant追加時の対応漏れをコンパイル時に検出できない。
Java・Kotlin・TypeScript・Pythonは、単純な列挙とADT表現を別の機構に分ける。Javaのenumはクラスとして状態・メソッドを持てるが、ADT用途はsealed interface + recordを組み合わせる。Kotlinも同様に、単純な列挙はenum class、payload付きのADTはsealed class / sealed interfaceが担う。どちらもswitch expressionやwhenとsealed階層を組み合わせれば網羅性を検査できる。TypeScriptのenumはGoに近い定数寄りの機能で、payload付きのADT表現にはliteral union(discriminated union)を使うのが実務上の主流であり、switch自体に網羅性チェックはないがnever型への代入という慣用句で擬似的に検証できる。PythonのbackedなENUM(enum.Enum)はpayloadを持たず、match文自体に網羅性チェックは基本的にない。PHPのbacked enumも文字列/整数値を持てるがpayloadは持てず、match式の網羅漏れはコンパイル時ではなく実行時のUnhandledMatchErrorとして現れる。
Rust・Swift・Scala・MoonBit・Zigは、enum自体がpayload付きのADTとして設計されている。Rustのenumは各variantが異なるフィールド構成を持てるdiscriminated unionで、matchは全variantの網羅をコンパイラが要求する。Swiftのenumはassociated valueを持て、switchも同様に全caseの網羅を要求する。Scala 3のenumもvariantごとに異なるフィールドを持てるADT表現に使え、sealedな階層へのmatchは網羅性チェックの対象になる。MoonBitのenumもconstructorがpayloadを持てるADT型で、matchは全constructorの網羅を要求する。Zigはenum単体では整数値ベースの単純な列挙にとどまるが、enumをタグとして持つunionを組み合わせたunion(enum)がpayload付きのvariantを安全に追跡するtagged unionとして機能し、switchは全variantの網羅を要求する。これらはmelangが目指すモデルに近い。
Haskell・OCaml・Elixirは、専用のenum型を持たないが、pattern matchingを中核にしたADT表現を持つ。Haskellのdata、OCamlのvariant typeは、コンストラクタごとに異なるデータを持てる代数的データ型で、パターンマッチの網羅漏れはコンパイラの警告として検出できる(Haskellは-Wincomplete-patterns、OCamlはデフォルトの警告)。Elixirには専用のenum型がなく、atomとtagged tupleによるpattern matchingでADT的に表現するが、網羅性はコンパイラではなく実行時のマッチ失敗(FunctionClauseError)として現れる。JavaScript・Rubyには組み込みのenumがなく、オブジェクトやsymbolで慣用的に代替するが、網羅性を検査する仕組み自体がない。
トレードオフ
enum専用の型機構を持たない設計(Go, C++, C#など)
- 利点: 学ぶ概念が増えず、既存の整数型や定数の仕組みだけで足りる。
- 欠点: variantが単なる整数・文字列定数になり、コンパイラは「この値が閉じた集合のどれか」を型として把握できない。
switchの網羅性チェックがなく、variant追加時の対応漏れを実行するまで発見できない。
単純な列挙とADTを別機構で用意する設計(Java, Kotlin, TypeScript, Pythonなど)
- 利点: 単純な列挙(状態フラグなど)には軽量な
enumを、複雑なモデリングにはsealed階層やliteral unionを使い分けられる。既存の型システムに乗せられるため、言語コアへの変更が少なくて済む。 - 欠点: 「単純な列挙」と「payload付きのSum Type」が別の構文・別の学習対象になり、どちらを使うべきかの判断が発生する。TypeScriptのようにコンパイラが網羅性を直接検査せず、
neverへの代入のような慣用句に頼る言語もある。
enum自体をpayload付きのADTとして設計する方式(Rust, Swift, Scala, MoonBit, Zig)
- 利点: 単純な列挙とpayload付きのSum Typeを同じ構文で扱え、学ぶ概念が一本化される。
matchの網羅性チェックにより、variant追加時の対応漏れをコンパイル時に検出できる。Option/Resultもこの仕組みだけで表現できる。 - 欠点: variantごとに異なるフィールドを持てる分、単純な整数列挙よりも型としては重く、シリアライズや外部表現との対応づけを別途設計する必要がある。Zigのように
enumとunionを組み合わせる設計では、payload付きのADTを表すのに2つの機構を理解する必要がある。
専用のenum型を持たずpattern matching + data/variant typeで表現する方式(Haskell, OCaml, Elixir)
- 利点: enum専用の構文を増やさず、既存のパターンマッチング機構だけでADTを表現できる。
- 欠点: 網羅性チェックが警告どまり(Haskell, OCaml)だったり、実行時のマッチ失敗としてしか現れなかったり(Elixir)と、コンパイルエラーとして強制されるとは限らない。
melangの選択
melangは、Rust・Swift・MoonBit・Zigと同じく、enum自体をpayload付きのADTとして設計し、matchの網羅性チェックをコンパイラに強制させる方式を選ぶ。
- Pros: 単純な列挙とpayload付きのSum Typeを同じ言語機能で扱えるため、学ぶ概念が一本化される。variant追加時に既存の
match不足をコンパイルエラーとして検出でき、Option/Resultや状態モデリングを一貫した仕組みに寄せられる。 - Cons: Go方式の「整数定数の集まり」に比べて、enumという型そのものの意味論(暗黙変換の禁止、外部表現との変換、variantのフィールド設計)を別途詰める必要がある。JSON / DB / OpenAPI等へのserialization表現や、discriminant値の指定可否は今後の検討課題として残る。
Go方式の軽量さよりも、列挙可能な状態集合を型システムが把握できることと、パターンマッチの網羅性検査を優先する。