Difference between revisions of "型"

From HaskellWiki
Jump to navigation Jump to search
 
(4 intermediate revisions by the same user not shown)
Line 33: Line 33:
 
==型と新しい型==
 
==型と新しい型==
   
  +
他のHaskellプログラムに型を導入する方法は<hask>type</hask>と<hask>newtype</hask>を用いて行う方法です。
The other two ways one may introduce types to Haskell programs are via the
 
<hask>type</hask> and <hask>newtype</hask> statements.
 
   
  +
<hask>type</hask>はあるデータコンストラクタに対してシノニムを与えます。<hask>newtype</hask>ではある型をリネームして新しいコンストラクタを作成する必要があります。
<hask>type</hask> introduces a synonym for a type and uses the same data
 
constructors. <hask>newtype</hask> introduces a renaming of a type and
 
requires you to provide new constructors.
 
   
  +
<hask>type</hask>宣言をするときは、型シノニムとその基底型はほぼいつでも交換可能です。([[instance]]宣言を扱う場合にいくつか制限があります)たとえば、このような宣言があったとします。
When using a <hask>type</hask> 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:
 
 
<haskell>
 
<haskell>
 
type Name = String
 
type Name = String
 
</haskell>
 
</haskell>
  +
<hask>String</hask>をシグネチャにもつどのような[[関数]]も<code>Name</code>型の要素に対して用いることができます。
then any [[function]] you had declared that had <hask>String</hask> in its
 
signature could be used on any element of type <code>Name</code>
 
   
  +
しかしながら、もしこのような宣言の場合:
However, if one had the declaration:
 
 
<haskell>
 
<haskell>
 
newtype FirstName = FirstName String
 
newtype FirstName = FirstName String
 
</haskell>
 
</haskell>
  +
先ほどの例のようにはいきません。関数の宣言においては実際に'''FirstName'''に対して定義されてなければいけません。このような要求を軽減するために、しばしばデコンストラクタを宣言します。こんな具合です:
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.:
 
 
<haskell>
 
<haskell>
 
unFirstName :: FirstName -> String
 
unFirstName :: FirstName -> String
 
unFirstName (FirstName s) = s
 
unFirstName (FirstName s) = s
 
</haskell>
 
</haskell>
  +
これはよく<code>newtype</code>内の[[field]]を使うときに行われます。(フィールドを幅広く使う人もいる一方で、多くの人がHaskellのフィールド実装は甘いと考えています。[[Programming guidelines]]や[[Future of Haskell]]も参照してください)
This is often done by the use of [[field]]s in the <code>newtype</code>. (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.
 
 
<haskell>
 
<haskell>
 
data Suit = Club | Diamond | Heart | Spade
 
data Suit = Club | Diamond | Heart | Spade
Line 77: Line 68:
 
deriving (Read, Show, Enum, Eq, Ord)
 
deriving (Read, Show, Enum, Eq, Ord)
 
</haskell>
 
</haskell>
  +
この例ではスートと数字を / から[[String]]やIntに変換できるように[[deriving]]節を使っています。これによって同値性や順序を確認できます。型変数がないような型を使うことで、同値性はどのコンストラクタが使われたかに依存し、順序は記述の順番に依存します。例えば<code>Three</code>は<code>Queen</code>よりも小さいです。
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 variable]]s, equality is based upon which constructor is used and order by the order you wrote them. e.g. <code>Three</code> is less than <code>Queen</code>.
 
   
Now we define an actual <code>Card</code>
+
では実際に<code>Card</code>を定義してみましょう。
 
<haskell>
 
<haskell>
 
data Card = Card {value::CardValue,
 
data Card = Card {value::CardValue,
Line 86: Line 76:
 
deriving (Read, Show, Eq)
 
deriving (Read, Show, Eq)
 
</haskell>
 
</haskell>
  +
この定義では[[field]]を使っています。これによって<code>Card</code>の2箇所に対してアクセスする既成の関数が手に入ります。また、もう一度いいますが、型変数([[type variables]])が使われていませんが、データの[[constructor]]は2つの引数に対して特定の型、つまり<code>CardValue</code>と<code>Suit</code>であることを要求しています。
In this definition, we use [[field]]s, which give us ready made functions to
 
access the two parts of a <code>Card</code>. Again, [[type variables]] were not
 
used, but the data [[constructor]] requires its two parameters to be of
 
specific types, <code>CardValue</code> and <code>Suit</code>.
 
   
The deriving clause here only specifies three of our desired [[Type class|Class]]es, we supply [[instance]] declarations for [[Ord]] and [[Enum]].
+
ここでのderiving節は単に求められている3つの[[Type class|Class]]を明記しているだけです。[[instance]]宣言を[[Ord]][[Enum]]に対して行います。
 
<haskell>
 
<haskell>
 
instance Ord Card where
 
instance Ord Card where
Line 101: Line 88:
 
fromEnum c = 4*(fromEnum (value c)) + (fromEnum (suit c))
 
fromEnum c = 4*(fromEnum (value c)) + (fromEnum (suit c))
 
</haskell>
 
</haskell>
  +
最後に、<code>Deck</code>型を<code>Card</code>のリストに対するエイリアスとして宣言し、デッキをリスト内包([[list comprehension]])として表します。
Finally, we alias the type <code>Deck</code> to a list of <code>Card</code>s
 
and populate the deck with a [[list comprehension]]
 
 
<haskell>
 
<haskell>
 
type Deck = [Card]
 
type Deck = [Card]
Line 112: Line 98:
 
==追加してください==
 
==追加してください==
   
  +
より図解が多い例を歓迎します。
Further illustrative examples would be most appreciated.
 
   
 
==参考==
 
==参考==
  +
データ[[constructor]]や[[Type class|class]]に関する(必要な)記事を読んでみてください。また[http://haskell.org/definition/haskell98-report.pdf Haskell 98 report]やご自分が選んだ実装([[GHC/Documentation]]など)にも最新の情報があるかもしれません。R
Read the (wanted) articles about data [[constructor]]s and [[Type class|class]]es. As well the
 
[http://haskell.org/definition/haskell98-report.pdf 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
 
*[[Determining the type of an expression]] - Let the Haskell compiler compute the type of expression
Line 130: Line 114:
 
*[[Research_papers/Type_systems]] allow the curious to delve deeper.
 
*[[Research_papers/Type_systems]] allow the curious to delve deeper.
   
Languages: [[Types|en]]
+
Languages: [[Type|en]]

Latest revision as of 14:11, 7 December 2009

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

では実際に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]]

追加してください

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

参考

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

Languages: en