From HaskellWiki
Revision as of 13:41, 7 December 2009 by Ymotongpoo (talk | contribs)

Jump to: navigation, search

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

データの宣言

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です。

型と新しい型

他の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も参照してください)

簡単な例

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

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

 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よりも小さいです。

Now we define an actual Card

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

In this definition, we use fields, which give us ready made functions to access the two parts of a Card. Again, type variables were not used, but the data constructor requires its two parameters to be of specific types, CardValue and Suit.

The deriving clause here only specifies three of our desired Classes, we supply instance declarations for Ord and Enum.

 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))

Finally, we alias the type Deck to a list of Cards and populate the deck with a list comprehension

 type Deck = [Card]

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

追加してください

Further illustrative examples would be most appreciated.

参考

Read the (wanted) articles about data constructors and classes. As well the Haskell 98 report and your chosen implementation (e.g. GHC/Documentation) have the latest words.

Languages: en