ししちにじゅうはち 4x7=28

よんたったー https://twitter.com/keita44_f4

列挙型と代数的データ型

今日はなごやかJava第1回 - connpassに行って来ました。
そこで@bleisさんによるJavaC#(とF#)の比較の話の中で列挙型(と代数的データ型)について話題に上がりました。
SML好きな自分が最近C++を書いてて同じように2つの違いについて思ったことがあったので、ここに書いておきます。

列挙型

  • (おそらく)有限数状態を表すために作られた
    • int等でもプログラマが管理すれば同様の機能は実現できる
    • 状態の意味を名前付けしたり、状態数の制限を付加できる
  • C/C++Javaなどで使える*1
  • ラベルのみを持つ
  • 使用例
// C++

// 定義
enum Janken {
  Gu,
  Choki,
  Pa
};

// 使用
int walkLength(Janken hand)
{
  switch(hand) {
    Gu:    return 3 // グリコ
    Choki: return 6 // チョコレート
    Pa:    return 6 // パイナップル
  }
}

代数的データ型

  • 数学の直和が理論の元になっている
    • 集合A,Bに対して2つの直和A+Bは A+B = { a_label(x) | x∈A } ∪ { b_label(x) | x∈B }
    • つまり、どちらの集合から来たかラベルが付いた和集合
  • HaskellOCamlやSMLやScalaで使える
  • 型推論が効く
    • 強力なパターンマッチが使える
  • 列挙型と違い、ラベルだけでなくデータも持てる
    • そもそも列挙ではなく、複数の型を直和した別な型をつくる概念
    • Cでいう共用体(union)にラベルが付いている、ほうが正しく表していると思う
  • 暗黙の型変換は存在しない
  • 使用例
(* SML *)

(* 定義 *)
datatype janken =
    Gu
  | Choki
  | Pa
  | LocalRule of string * int

(* 使用 *)
fun walkLength hand = (* 型推論結果: fn: janken -> int *)
  case hand of
      Gu    => 3
    | Choki => 6
    | Pa    => 6
    | LocalRule (name, n) => n (* "グリコノオマケ"、など *)

まとめ

  • 2つの生まれ元は違えど、使いたい目的はたいてい同じ「ケース分け」
    • しかし、代数的データ型のほうが強い
  • 個人的には代数的データ型がサクッと使いたいんです!!
    • C++で代数的データ型を探して絶望したのよ…
    • もちろんクラスを駆使すればC++/Javaで同様のことはできる*2
    • もっと多くの言語に流行れ、でも原理的な問題で実装難しそう

*1:Cでは実体はただのint、C++では型や値を指定できるものの暗黙の変換とかがあってやっかいこの上ない

*2:そんなにがんばりたくない