型
Haskellでは 型 とはプログラム内で用いるデータを表現するものです。
データの宣言
Haskellでは型をdata
宣言を通じて導入、または宣言します。一般的にデータ宣言はこのように行います。
data [context =>] type tv1 ... tvi = con1 c1t1 c1c2... c1tn | ... | conm cmt1 ... cmtq [deriving]
まだHaskellをあまり理解していいない方にはおそらく説明になっていないかもしれません。
上の宣言の本質は、data
キーワードを使って、付属的なコンテキストを与え、型の名前と多くのtype variableを与えることにあります。
その後、多くの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です。
型と新しい型
The other two ways one may introduce types to Haskell programs are via the
type
and newtype
statements.
type
introduces a synonym for a type and uses the same data
constructors. newtype
introduces a renaming of a type and
requires you to provide new constructors.
When using a type
declaration, the type synonym and its base type
are interchangeble almost everywhere (There are some restrictions when dealing with instance declarations). For example, if you had the declaration:
type Name = String
then any function you had declared that had String
in its
signature could be used on any element of type Name
However, if one had the declaration:
newtype FirstName = FirstName String
this would no longer be the case. Functions would have to be declared that actually were defined on FirstName. Often, one creates a deconstructor at the same time which helps alleviate this requirement. e.g.:
unFirstName :: FirstName -> String
unFirstName (FirstName s) = s
This is often done by the use of fields in the newtype
. (Note
that many consider the Haskell field implementation sub-optimal, while
others use it extensively. See Programming guidelines and Future of Haskell)
簡単な例
Suppose you want to create a program to play bridge. You need something to represent cards. Here is one way to do that.
First, create data types for the suit and card number.
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)
Each of these uses a deriving clause to allow us to convert them from / to String and Int, test them for equality and ordering. With types like this,
where there are no type variables, equality is based upon which constructor is used and order by the order you wrote them. e.g. Three
is less than Queen
.
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 Card
s
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.
- 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