型
From HaskellWiki
Haskellでは 型 とはプログラム内で用いるデータを表現するものです。
Contents |
1 データの宣言
Haskellでは型をdata宣言を通じて導入、または宣言します。一般的にデータ宣言はこのように行います。
data [context =>] type tv1 ... tvi = con1 c1t1 c1c2... c1tn |
... | conm cmt1 ... cmtq
[deriving]
まだHaskellをあまり理解していいない方にはおそらく説明になっていないかもしれません。
上の宣言の本質は、その後、多くのconstructorが続きます。これらはtype variableかtype constantのリストになっています。最後に付属的にderivingが続きます。
他にも多くの微妙な事柄が必要です。たとえばデータコンストラクタに必要なパラメータはeagerでなければならない、どんなclassがderivingの中で許可されているか、コンストラクタ内のfield名の使い方、contextが実際になにをするのかなどです。これらについてはそれぞれの記事を参照してください。
いくつかの例を見てみましょう。Haskellの標準データ型Maybeは普通このように宣言されています。
data Maybe a = Just a | Nothing
これは、Maybe型はaで表される1つの型変数を持っていて、JustとNothingという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です。
2 型と新しい型
他のHaskellプログラムに型を導入する方法はtype Name = String
Name型の要素に対して用いることができます。
しかしながら、もしこのような宣言の場合:
newtype FirstName = FirstName String
先ほどの例のようにはいきません。関数の宣言においては実際にFirstNameに対して定義されてなければいけません。このような要求を軽減するために、しばしばデコンストラクタを宣言します。こんな具合です:
unFirstName :: FirstName -> String unFirstName (FirstName s) = s
これはよくnewtype内のfieldを使うときに行われます。(フィールドを幅広く使う人もいる一方で、多くの人がHaskellのフィールド実装は甘いと考えています。Programming guidelinesやFuture of Haskellも参照してください)
3 簡単な例
たとえばトランプのゲームのブリッジを実装することを考えてみましょう。まずカードを表す何かが必要です。一つの例としてはこんなかんじです。
まず、スートと数を表すデータ型を作成します。
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節を使っています。これによって同値性や順序を確認できます。型変数がないような型を使うことで、同値性はどのコンストラクタが使われたかに依存し、順序は記述の順番に依存します。例えばThreeはQueenよりも小さいです。
では実際にCardを定義してみましょう。
data Card = Card {value::CardValue, suit::Suit} deriving (Read, Show, Eq)
この定義ではfieldを使っています。これによってCardの2箇所に対してアクセスする既成の関数が手に入ります。また、もう一度いいますが、型変数(type variables)が使われていませんが、データのconstructorは2つの引数に対して特定の型、つまりCardValueとSuitであることを要求しています。
ここでのderiving節は単に求められている3つのClassを明記しているだけです。instance宣言をOrdや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))
最後に、Deck型をCardのリストに対するエイリアスとして宣言し、デッキをリスト内包(list comprehension)として表します。
type Deck = [Card] deck::Deck deck = [Card val su | val <- [Two .. Ace], su <- [Club .. Spade]]
4 追加してください
より図解が多い例を歓迎します。
5 参考
データconstructorやclassに関する(必要な)記事を読んでみてください。またHaskell 98 reportやご自分が選んだ実装(GHC/Documentationなど)にも最新の情報があるかもしれません。R
- Determining the type of an expression - Let the Haskell compiler compute the type of expression
- Language extensions - many language extensions are to do with changes to the type system.
- Smart constructors shows some interesting examples including a non-trivial usage of
newtype. - Unboxed type shows ways to have values closer to the bare metal :).
- Phantom type discusses types without constructors.
- Type witness gives an example of GADTs, a GHC extension.
- Existential type shows how to implement a common O-O programming paradigm.
- Type arithmetic implements the Peano numbers.
- Reified type, Non-trivial type synonyms, Abstract data type, Concrete data type, Algebraic data type.
- Research_papers/Type_systems allow the curious to delve deeper.
Languages: en
