From HaskellWiki

Haskellでは とはプログラム内で用いるデータを表現するものです。

データの宣言[edit]

Haskellでは型をdata宣言を通じて導入、または宣言します。一般的にデータ宣言はこのように行います。

data [context =>] type tv1 ... tvi = con1  c1t1 c1c2... c1tn |
                      ... | conm cmt1 ... cmtq
                    [deriving]

まだHaskellをあまり理解していいない方にはおそらく説明になっていないかもしれません。

上の宣言の本質は、dataキーワードを使って、付属的なコンテキストを与え、型の名前と多くのtype variableを与えることにあります。 その後、多くのconstructorが続きます。これらはtype variabletype constantのリストになっています。最後に付属的にderivingが続きます。

他にも多くの微妙な事柄が必要です。たとえばデータコンストラクタに必要なパラメータはeagerでなければならない、どんなclassderivingの中で許可されているか、コンストラクタ内のfield名の使い方、contextが実際になにをするのかなどです。これらについてはそれぞれの記事を参照してください。

いくつかの例を見てみましょう。Haskellの標準データ型Maybeは普通このように宣言されています。

 data Maybe a = Just a | Nothing

これは、Maybe型はaで表される1つの型変数を持っていて、JustNothingという2つのconstructorを持っているということを意味しています。(Haskellでは型名とコンストラクタ名は大文字で始まらなければいけないことに注意してください) Justコンストラクタは1つのパラメータ"a"をとります。

他の例として、二分木 (binary Tree)を考えてみましょう。このように表されます。

 data Tree a = Branch (Tree a) (Tree a) | Leaf a

ここで、Treeのコンストラクタの1つBranchはコンストラクタのパラメータには2つのtreeを取る一方で、Leafは型変数"a"だけを取ります。Haskellではこういった再帰型は非常によく使われるpatternsです。

型と新しい型[edit]

他のHaskellプログラムに型を導入する方法はtypenewtypeを用いて行う方法です。

typeはあるデータコンストラクタに対してシノニムを与えます。newtypeではある型をリネームして新しいコンストラクタを作成する必要があります。

type宣言をするときは、型シノニムとその基底型はほぼいつでも交換可能です。(instance宣言を扱う場合にいくつか制限があります)たとえば、このような宣言があったとします。

 type Name = String

Stringをシグネチャにもつどのような関数Name型の要素に対して用いることができます。

しかしながら、もしこのような宣言の場合:

 
  newtype FirstName = FirstName String

先ほどの例のようにはいきません。関数の宣言においては実際にFirstNameに対して定義されてなければいけません。このような要求を軽減するために、しばしばデコンストラクタを宣言します。こんな具合です:

  unFirstName :: FirstName -> String
  unFirstName (FirstName s) = s

これはよくnewtype内のfieldを使うときに行われます。(フィールドを幅広く使う人もいる一方で、多くの人がHaskellのフィールド実装は甘いと考えています。Programming guidelinesFuture of Haskellも参照してください)

簡単な例[edit]

たとえばトランプのゲームのブリッジを実装することを考えてみましょう。まずカードを表す何かが必要です。一つの例としてはこんなかんじです。

まず、スートと数を表すデータ型を作成します。

 data Suit = Club | Diamond | Heart | Spade
     deriving (Read, Show, Enum, Eq, Ord)

 data CardValue = Two | Three | Four
     | Five | Six | Seven | Eight | Nine | Ten 
     | Jack | Queen | King | Ace
    deriving (Read,  Show, Enum, Eq, Ord)

この例ではスートと数字を / からStringやIntに変換できるようにderiving節を使っています。これによって同値性や順序を確認できます。型変数がないような型を使うことで、同値性はどのコンストラクタが使われたかに依存し、順序は記述の順番に依存します。例えばThreeQueenよりも小さいです。

では実際にCardを定義してみましょう。

 
 data Card = Card {value::CardValue, 
                    suit::Suit}
    deriving (Read, Show, Eq)

この定義ではfieldを使っています。これによってCardの2箇所に対してアクセスする既成の関数が手に入ります。また、もう一度いいますが、型変数(type variables)が使われていませんが、データのconstructorは2つの引数に対して特定の型、つまりCardValueSuitであることを要求しています。

ここでのderiving節は単に求められている3つのClassを明記しているだけです。instance宣言をOrdEnumに対して行います。

 instance Ord Card where
      compare c1 c2  | (value c1 == (value c2)) = compare (suit c1) (suit c2)
                     | otherwise = compare (value c1) (value c2)

 instance Enum Card where
      toEnum n = Card (toEnum (n `div` 4)) (toEnum (n `mod` 4))
      fromEnum c =  4*(fromEnum (value c)) + (fromEnum (suit c))

最後に、Deck型をCardのリストに対するエイリアスとして宣言し、デッキをリスト内包(list comprehension)として表します。

 type Deck = [Card]

 deck::Deck
 deck = [Card val su | val <- [Two .. Ace], su <- [Club .. Spade]]

追加してください[edit]

より図解が多い例を歓迎します。

参考[edit]

データconstructorclassに関する(必要な)記事を読んでみてください。またHaskell 98 reportやご自分が選んだ実装(GHC/Documentationなど)にも最新の情報があるかもしれません。R

Languages: en