代数的データ型

代数的データ型（だいすうてきデータがた、英: algebraic data type）とはプログラミング、特に関数型プログラミングや型システムにおいて使われるデータ型である。それぞれの代数的データ型の値には、1個以上のコンストラクタがあり、各コンストラクタには0個以上の引数がある。 代数的データ型の値（データ）の感覚的な説明としては、引数で与えられた他のデータ型の値を、コンストラクタで包んだようなもの、である。コンストラクタに引数がある代数データ型は複合型（他のデータ型を組み合わせて形成する型）である。 Haskellにおける、葉に整数型の値を持つ（分岐は部分木しか持たない）、二分木の例で説明する。以下のようなdata宣言で、データ型を宣言する。 data Node = Leaf Integer | Branch Node Node deriving (Show) -- 表示させて確認するために付加してあるもので、必須ではない。 この宣言でNodeという名前の型を宣言している（Haskellでは型名の先頭は大文字でなければならない）。縦棒（"|"）で区切って、各コンストラクタによる形を並べる。LeafとBranchはコンストラクタ（データコンストラクタ）である。コンストラクタLeafは1個のIntegerを引数として取り、Branchは2個のNodeを引数として取る（再帰データ型の例にもなっている）。Haskellではコンストラクタの名前も、先頭は大文字でなければならない（ここでは避けたが、型とコンストラクタに同じ名前を使っても構わない）。 Haskellインタプリタghciで、この型の値を入力し表示させた例を示す。 *Main> Leaf 1 Leaf 1 *Main> Branch (Branch (Leaf 1) (Leaf 2)) (Branch (Leaf 3) (Leaf 4)) Branch (Branch (Leaf 1) (Leaf 2)) (Branch (Leaf 3) (Leaf 4)) 中のデータにアクセスするにはパターンマッチングを使う。ここで定義した型の木の深さを返す関数の例で次に示す。 depth tree = case tree of Leaf _ -> 1 Branch a b -> 1 + max (depth a) (depth b) 基本的な代数データ型としては、多くの関数型言語において、言語組み込みのリスト型が用意されており、空リストのためのコンストラクタに相当するリテラルと、追加したい要素と残りのリストを引数に取るコンストラクタ（Lispのen:cons）に相当する、中置演算子風のコンストラクタ（ ":" など）が言語組み込みで用意されている。 代数的データ型の特殊な例として、直積型（1つのコンストラクタだけを持つ）と列挙型（引数なしの多くのコンストラクタを持つ）がある。 前述の二分木の例において、コンストラクタLeafは Integer -> Node という型を、コンストラクタBranchは Node -> Node -> Node という型を持つ。型のみを見た場合、関数と同じ型をしている。しかし、関数とは違いコンストラクタは単にそこにあるだけのものであり、評価（実行）されるものではなく、オブジェクト指向言語におけるコンストラクタとは異なる。式として見た場合、関数に引数を適用する式は簡約可能だが、コンストラクタによる式は全体としてはそれ以上簡約できない、値をあらわす式である。 関数型言語で抽象データ型を実現する手法のひとつに、モジュールシステムによるスコープ制限を利用して、コンストラクタを掩蔽し、型のみを公開する、という手法がある。データコンストラクタそのものの代わりに、相当する引数をとって、目的の型の値を返すような、コンストラクタを抽象化した関数を定義し、そちらの関数を公開する。この関数が、オブジェクト指向言語におけるコンストラクタに相当する。 OCamlではヴァリアント型と言い、前述の二分木と同等のデータ型は、次のように書く。 type node = Leaf of int | Branch of node * node また、伝統的なMLではdatatypeというキーワードを使う。いずれも、ofの後に1個しか型を指定できないので、Branchのように組み込みの直積型であるタプルを併用する必要がある。MLでもコンストラクタの先頭は大文字だが、型名の先頭は小文字である。 Haskellの場合と同様にして、インタプリタ上で値を作る例と深さを返す関数の例を示す。 # Leaf 1;; - : node = Leaf 1 # Branch (Branch (Leaf 1, Leaf 2), Branch (Leaf 3, Leaf 4));; - : node = Branch (Branch (Leaf 1, Leaf 2), Branch (Leaf 3, Leaf 4)) let rec depth tree = match tree with Leaf _ -> 1 | Branch (a, b) -> 1 + max (depth a) (depth b) Visual Prologでは次のように書く。 domains tree = empty(); leaf(integer Leaf); node(tree Left, tree Right). この例では、leafとnodeの他に、空の木を示すemptyがある。 集合論において代数的データ型と等価なものとして直和がある。この集合の各元はタグ（コンストラクタと等価）とそのタグに対応する型のオブジェクト（コンストラクタの引数と等価）で構成される。 一般に代数的データ型は直積型の総和であり、再帰的に定義されることもある。各コンストラクタは直積型のタグとなって他と区別されるか、1つしかコンストラクタがない場合は、そのデータ型自体が直積型となる。さらにコンストラクタの引数の型が直積型の要素となる。引数のないコンストラクタは空に対応する。データ型が再帰的であるなら、その直積型の総和は再帰データ型となり、各コンストラクタによって再帰データ型が構成される。 例えば、以下のような Haskell のデータ型 data List a = Nil | Cons a (List a) を型理論的に表すと次のようになる。 λ α . μ β .1 + α × β {\displaystyle \lambda \alpha .\mu \beta .1+\alpha \times \beta } コンストラクタは次のようになる。 n i l α = r o l l   ( i n l   ⟨ ⟩ ) {\displaystyle \mathrm {nil} _{\alpha }=\mathrm {roll} \ (\mathrm {inl} \ \langle \rangle )} c o n s α   x   l = r o l l   ( i n r   ⟨ x , l ⟩ ) {\displaystyle \mathrm {cons} _{\alpha }\ x\ l=\mathrm {roll} \ (\mathrm {inr} \ \langle x,l\rangle )} この Haskell の List 型を型理論の別の形式で表すと、次のようになる。 μ ϕ . λ a .1 + α × ϕ   α {\displaystyle \mu \phi .\lambda a.1+\alpha \times \phi \ \alpha } μ {\displaystyle \mu } と λ {\displaystyle \lambda } が2つの定義で順序が入れ替わっている点に注意されたい。前者の形式は再帰型を本体とする型関数の定義であり、後者は型の再帰関数定義である。型変数 ϕ {\displaystyle \phi } は、これが β {\displaystyle \beta } のような基本型ではなく関数型であることを示している（ ϕ {\displaystyle \phi } はギリシャ文字で "f" に相当する）。また、型本体の中の引数型 α {\displaystyle \alpha } に関数 ϕ {\displaystyle \phi } を適用しなければならない。 List の例の用途から考えると、これら2つの定式化に大きな違いはないが、後者の形式は「入れ子データ型; nested data type」と呼ばれる表現を可能とする。入れ子データ型とは、オリジナルとパラメータ的に異なる再帰型を派生させるものである。詳しくは、 Richard Bird、Lambert Meertens、Ross Paterson らの研究を参照されたい。 Algebraic data type in The Free On-line Dictionary of Computing, Editor Denis Howe. この記事は2008年11月1日以前にFree On-line Dictionary of Computingから取得した項目の資料を元に、GFDL バージョン1.3以降の「RELICENSING」(再ライセンス) 条件に基づいて組み込まれている。