Haskellタイプとデータコンストラクタ


124

Learnyouahaskell.comからHaskellを学んでいます。タイプコンストラクターとデータコンストラクターを理解できません。たとえば、私はこれの違いを本当に理解していません:

data Car = Car { company :: String  
               , model :: String  
               , year :: Int  
               } deriving (Show) 

この:

data Car a b c = Car { company :: a  
                     , model :: b  
                     , year :: c   
                     } deriving (Show)  

1つ目は、1つのコンストラクタ(Car)を使用してタイプのデータを構築するだけだと理解していCarます。2つ目はよくわかりません。

また、データ型は次のように定義されていますか?

data Color = Blue | Green | Red

このすべてに適合しますか?

私が理解していることから、3番目の例(Color)は、3つの状態(BlueGreenまたは)のタイプになりますRed。しかし、それは私が最初の2つの例をどのように理解するかと矛盾します。それは、型Carが1つの状態にしかできないというCarことですか?もしそうなら、2番目の例はどのように適合しますか?

基本的に、上記の3つのコード例/構成を統合する説明を探しています。


18
Carの例はCar、型コンストラクター(の左側=)とデータコンストラクター(右側)の両方であるため、少しわかりにくいかもしれません。最初の例では、Car型コンストラクターは引数を取らず、2番目の例では3つ引数を取ります。どちらの例でも、Carデータコンストラクターは3つの引数を取ります(ただし、これらの引数の型は1つの場合は固定され、他の場合はパラメーター化されます)。
Simon Shine

1つ目は、1つのデータコンストラクター(Car :: String -> String -> Int -> Car)を使用してタイプのデータを作成することですCar。2つ目は、単純に1つのデータコンストラクター(Car :: a -> b -> c -> Car a b c)を使用してタイプのデータを作成する方法ですCar a b c
ネス

回答:


228

data宣言、型コンストラクタは、等号の左側のものです。データコンストラクタ(S)等号の右側のものです。型が期待される場所では型コンストラクタを使用し、値が期待される場所ではデータコンストラクタを使用します。

データコンストラクター

簡単にするために、色を表すタイプの例から始めることができます。

data Colour = Red | Green | Blue

ここには、3つのデータコンストラクタがあります。Colourは型であり、型のGreen値を含むコンストラクタですColour。同様に、RedおよびBlueは、両方ともtypeの値を構築するコンストラクタですColour。私たちはそれをスパイスアップすることを想像することができました!

data Colour = RGB Int Int Int

型だけが残ってColourいますRGBが、値ではありません。3つのIntを取り、値を返す関数です。RGBタイプがあります

RGB :: Int -> Int -> Int -> Colour

RGBいくつかのを引数として取り、それらを使用して新しい値を構築する関数であるデータコンストラクターです。オブジェクト指向プログラミングを実行したことがある場合は、これを認識してください。OOPでは、コンストラクターもいくつかの値を引数として取り、新しい値を返します!

この場合、RGB3つの値に適用すると、色の値が取得されます。

Prelude> RGB 12 92 27
#0c5c1b

私たちはしている値を構築タイプのをColourデータコンストラクタを適用することで。データコンストラクターは、変数のように値を含むか、他の値を引数として取り、新しいを作成します。以前のプログラミングを実行したことがある場合、この概念はそれほど奇妙なものではないはずです。

休憩

を格納するバイナリツリーを作成するString場合は、次のようなことを想像できます。

data SBTree = Leaf String
            | Branch String SBTree SBTree

ここに表示されているのは、SBTree2つのデータコンストラクターを含む型です。つまり、型の値を構成する2つの関数(つまりLeafおよびBranch)がありますSBTree。バイナリツリーの動作に慣れていない場合は、そこにいるだけで十分です。バイナリツリーがどのように動作するかを実際に知る必要はありませんString。このツリーが何らかの方法でsを保存しているということだけです。

また、両方のデータコンストラクターがString引数を取ることもわかります。これは、ツリーに格納する文字列です。

だが!も格納できるようにしたい場合はBool、新しいバイナリツリーを作成する必要があります。次のようになります。

data BBTree = Leaf Bool
            | Branch Bool BBTree BBTree

型コンストラクタ

SBTreeBBTreeは両方とも型コンストラクタです。しかし、明白な問題があります。それらがどれほど似ているかわかりますか?これは、どこかに本当にパラメータが必要であることを示しています。

これを行うことができます:

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

次に、型コンストラクターのパラメーターとして型変数 を導入しaます。この宣言でBTreeは、関数になっています。引数としてを取り、新しい型を返します。

それは違いを検討するためにここで重要なのは、具体的なタイプ(例としてはInt[Char]そしてMaybe Boolあなたのプログラム内の値に割り当てることができるタイプである)を、そして型コンストラクタ関数あなたはタイプを供給する必要があることができるようにするには値に割り当てられます。「何かのリスト」である必要があるため、値のタイプを「リスト」にすることはできません。同じ趣旨で、値は「何かを格納するバイナリツリー」である必要があるため、「バイナリツリー」タイプにすることはできません。

たとえばBool、引数としてを渡すと、s を格納するバイナリツリーであるBTreeタイプを返します。型変数のすべての発生を置き換えタイプで、そしてあなたはそれが本当だか自分で見ることができます。BTree BoolBoolaBool

必要に応じて、種類をBTree関数として表示できます

BTree :: * -> *

種類はややタイプに似ています–は*具体的なタイプを示すのでBTree、具体的なタイプから具体的なタイプへと言います。

まとめ

ここで少し戻って、類似点に注意してください。

  • データコンストラクタは、 0以上かかる「機能」である値を、新たな価値をあなたに戻っています。

  • 型コンストラクタは、 0以上かかる「機能」であるタイプを、あなたに新しいタイプをバック与えます。

値にわずかなバリエーションが必要な場合、パラメーター付きのデータコンストラクターはすばらしいです。これらのパラメーターのバリエーションをパラメーターに入れ、値を作成する人にどの引数を入れるかを決定させます。同じ意味で、パラメーターを持つタイプコンストラクターはすばらしいですタイプにわずかなバリエーションが必要な場合!これらのバリエーションをパラメーターとして配置し、型を作成する人に、どの引数を入力するかを決定させます。

ケーススタディ

ここのホームストレッチとして、Maybe aタイプを考えることができます。その定義は

data Maybe a = Nothing
             | Just a

以下Maybeは具象型を返す型コンストラクタです。Just値を返すデータコンストラクターです。Nothing値を含むデータコンストラクターです。のタイプJustを見ると、

Just :: a -> Maybe a

つまり、Justtypeの値を取り、type aの値を返しますMaybe a。の種類Maybeを見ると、

Maybe :: * -> *

つまり、Maybe具象型を取り、具象型を返します。

もう一度!具体的な型と型コンストラクタ関数の違い。Maybesのリストを作成することはできません-実行しようとした場合

[] :: [Maybe]

エラーが発生します。ただしMaybe Int、またはのリストを作成できますMaybe a。これMaybeはが型コンストラクタ関数であるためですが、リストには具象型の値を含める必要があります。Maybe IntおよびMaybe a具象型です(または、必要に応じて、具象型を返す型コンストラクタ関数の呼び出し)。


2
最初の例では、RED GREENとBLUEはどちらも引数を取らないコンストラクターです。
OllieB 2013

3
data Colour = Red | Green | Blue「コンストラクターがまったくない」という主張は明らかに間違っています。型コンストラクターとデータコンストラクターは引数を取る必要はありません。たとえば、「kip.node / haskellwiki / Constructor」を参照してdata Tree a = Tip | Node a (Tree a) (Tree a)、「2つのデータコンストラクター、TipとNodeがあります」を参照してください。
Frerich Raabe 2013

1
@CMCDragonkai正解です!種類は「タイプのタイプ」です。タイプと値の概念を結合する一般的なアプローチは、依存型タイピングと呼ばれますIdrisはHaskellに触発された依存型言語です。適切なGHC拡張を使用すると、Haskellでの依存型入力に多少近づくこともできます。(一部の人々は、「Haskellの研究は、依存型を持たずに考え出すどれだけ近いかを我々が得ることができる依存型しようとしている。」と冗談を言ってきた)
KQR

1
@CMCDragonkai標準のHaskellで空のデータ宣言を持つことは実際には不可能です。しかし、それを可能にするGHC拡張(-XEmptyDataDecls)があります。あなたが言うように、そのタイプには値がないので、f :: Int -> Zたとえば、関数は決して戻らない場合があります(何が戻されるのでしょうか?)ただし、タイプが必要だが実際には値を気にしない場合に役立ちます
kqr 2014

1
本当にそれは不可能ですか?私はGHCで試したところ、エラーなく実行されました。私はGHC拡張をロードする必要はなく、バニラGHCだけをロードしました。それから私は書くことができ:k Z、それは私に星を与えただけです。
CMCDragonkai 2014

42

Haskellには代数的データ型があり、他の言語にはほとんどありません。これはおそらくあなたを混乱させるものです。

他の言語では、通常、「レコード」、「構造体」などを作成できます。これには、さまざまなタイプのデータを保持する一連の名前付きフィールドがあります。あなたはまた、時々 (小)に固定可能な値のセット(例えば、あなたの持っている、「列挙」を作ることができるRedGreenBlue)を。

Haskellでは、これらの両方を同時に組み合わせることができます。奇妙ですが、本当です!

なぜ「代数的」と呼ばれるのですか?まあ、オタクは「合計型」と「製品型」について話します。例えば:

data Eg1 = One Int | Two String

Eg1値は、基本的にはいずれかの整数、またはストリング。したがって、すべての可能なEg1値のセットは、すべての可能な整数値とすべての可能な文字列値のセットの「合計」です。したがって、オタクはEg1「合計タイプ」と呼ばれます。一方:

data Eg2 = Pair Int String

すべてのEg2値は、整数と文字列の両方で構成されています。したがって、すべての可能なEg2値のセットは、すべての整数のセットとすべての文字列のセットのデカルト積です。2つのセットは「乗算」されるため、これは「製品タイプ」です。

Haskellの代数型は積型の和型です。コンストラクタに複数のフィールドを指定して製品タイプを作成し、複数のコンストラクタを使用して(製品の)合計を作成します。

それが役立つ理由の例として、XMLまたはJSONとしてデータを出力し、構成レコードを取得するものがあるとします。ただし、XMLとJSONの構成設定はまったく異なります。だからあなたこのようなことをするかもしれません:

data Config = XML_Config {...} | JSON_Config {...}

(当然、適切なフィールドがいくつかあります。)通常のプログラミング言語では、このようなことはできません。そのため、ほとんどの人はそれに慣れていません。


4
すごい!ウィキペディアは、「それらは...ほとんどすべての言語で作成できる」と語っています。:)例えばC / ++では、それはunionsで、タグの規律があります。:)
Will Ness

5
ええ、でも私が言及するたびにunion、人々は「地獄は誰がそれを使うの?」;-)
MathematicalOrchid

1
私はunionCのキャリアで多くの人を使いました。それはそうではないのでそれを不必要に聞こえさせないでください。
TruthAdjuster 2018

26

最も単純なケースから始めます。

data Color = Blue | Green | Red

これは、「型コンストラクタ」を定義するColor引数を取りません-それは3「データコンストラクタ」を持って、BlueGreenRed。どのデータコンストラクターも引数を取りません。この手段は、そこタイプの3であることをColorBlueGreenRed

データコンストラクターは、ある種の値を作成する必要がある場合に使用されます。お気に入り:

myFavoriteColor :: Color
myFavoriteColor = Green

値作成しmyFavoriteColor使用してGreenデータコンストラクタを-とmyFavoriteColorタイプのものであろうColorことは、データコンストラクタによって生成される値の型だからです。

タイプコンストラクターは、何らかのタイプのタイプを作成する必要がある場合に使用されます。これは通常、署名を書き込む場合です。

isFavoriteColor :: Color -> Bool

この場合は、Color型のコンストラクター(引数を取らない)を呼び出しています。

まだ私と一緒に?

ここで、赤、緑、青の値を作成するだけでなく、「強度」も指定したいとします。同様に、0から256までの値です。各データコンストラクターに引数を追加することで、次のようになります。

data Color = Blue Int | Green Int | Red Int

これで、3つのデータコンストラクターのそれぞれがtypeの引数を取りますInt。型コンストラクタ(Color)はまだ引数を取りません。だから、私の好きな色は濃い緑なので、

    myFavoriteColor :: Color
    myFavoriteColor = Green 50

また、Greenデータコンストラクターを呼び出し、typeの値を取得しますColor

人々が色の強さを表現する方法を指示したくない場合を想像してみてください。私たちがしたように、数値が必要な人もいます。他のものは、「明るい」または「それほど明るくない」を示すブール値で問題ない場合があります。これに対する解決策Intは、データコンストラクターでハードコーディングするのではなく、型変数を使用することです。

data Color a = Blue a | Green a | Red a

これで、型コンストラクターは1つの引数(もう1つの型を呼び出すだけaです!)を受け取り、すべてのデータコンストラクターはその型の1つの引数(値!)を受け取りますa。だからあなたは

myFavoriteColor :: Color Bool
myFavoriteColor = Green False

または

myFavoriteColor :: Color Int
myFavoriteColor = Green 50

Colorデータコンストラクターによって返される「有効な」タイプを取得するために、引数(別のタイプ)を使用してタイプコンストラクターを呼び出す方法に注意してください。これは、1〜2杯のコーヒーについて読みたい種類の概念に触れています。

ここで、データコンストラクターと型コンストラクターとは何か、データコンストラクターが他の値を引数として受け取り、型コンストラクターが他の型を引数として受け取る方法を理解しました。HTH。


私は、あなたがnullaryデータコンストラクターの概念を理解しているとは思いません。Haskellで定数について話す一般的な方法であることは知っていますが、正しくないことが何度も証明されていませんか?
kqr 2013

@kqr:データコンストラクターをnullにすることができますが、関数ではなくなります。関数は、引数を取り、値を生成するものです。つまり->、シグニチャー内にあるものです。
Frerich Raabe 2013

値は複数の型を指すことができますか?または、すべての値が1つのタイプのみに関連付けられているのですか?
CMCDragonkai 2014

1
@jrgいくつかのオーバーラップがありますが、それは特に型コンストラクターのためではなく、型変数(たとえばain)のためdata Color a = Red aです。a任意の型のプレースホルダーです。普通の関数でも同じことができます。たとえば、型の関数(a, b) -> aは2つの値(型aとのb)のタプルを取り、最初の値を生成します。これはタプル要素のタイプを指示しないという点で「ジェネリック」関数です。それは、関数が最初のタプル要素と同じタイプの値を生成することを指定するだけです。
Frerich Raabe、2015

1
+1 Now, our type constructor takes one argument (another type which we just call a!) and all of the data constructors will take one argument (a value!) of that type a.これは非常に役立ちます。
ジョナス

5

他の人が指摘したように、ここではポリモーフィズムはそれほど有用ではありません。あなたがおそらくすでに知っている別の例を見てみましょう:

Maybe a = Just a | Nothing

この型には2つのデータコンストラクターがあります。Nothing少し退屈ですが、有用なデータは含まれていません。一方Just、値はa-のタイプに関係なく含まれaます。このタイプを使用する関数を記述してみましょう。たとえば、Intリストの先頭を取得する場合は、それを取得します(エラーをスローするよりも、これがより役立つことに同意してください)。

maybeHead :: [Int] -> Maybe Int
maybeHead [] = Nothing
maybeHead (x:_) = Just x

> maybeHead [1,2,3]    -- Just 1
> maybeHead []         -- None

したがって、この場合aはですが、Int他のタイプでも同様に機能します。実際、すべてのタイプのリストに対して関数を機能させることができます(実装を変更しなくても)。

maybeHead :: [t] -> Maybe t
maybeHead [] = Nothing
maybeHead (x:_) = Just x

一方、あなたは唯一の特定のタイプを受け入れる関数を書くことができMaybe、例えば

doubleMaybe :: Maybe Int -> Maybe Int
doubleMaybe Just x = Just (2*x)
doubleMaybe Nothing= Nothing

要するに、ポリモーフィズムを使用すると、他のさまざまな型の値を処理する柔軟性を独自の型に与えることができます。

あなたの例では、ある時点でString会社を特定するには不十分であると判断するかもしれませんが、独自のタイプCompany(国、住所、バックアカウントなどの追加データを保持する)が必要です。の最初の実装は、最初の値の代わりCarに使用するように変更する必要があります。2番目の実装は問題ありません。それをそのまま使用すると、以前と同じように機能します(もちろん、会社のデータにアクセスする機能を変更する必要があります)。CompanyStringCar Company String Int


別のデータ宣言のデータコンテキストで型コンストラクターを使用できますか?のようなものdata Color = Blue ; data Bright = Color?ghciで試してみましたが、型コンストラクタのColorは、Bright定義のColorデータコンストラクタとは関係がないようです。Colorコンストラクタは2つしかありません。1つはDataでもう1つはTypeです。
CMCDragonkai 2014

@CMCDragonkai私はあなたがこれを行うことができるとは思いません、そしてあなたがこれで何を達成したいのかさえわかりません。dataor を使用して既存のタイプを「ラップ」することができますnewtype(eg data Bright = Bright Color)、またはtype類義語を定義するために使用することができます(eg type Bright = Color)。
Landei、2014

5

2番目のものには、「多態性」の概念があります。

a b cいずれのタイプのものとすることができます。たとえば、aすることができ[String]b可能[Int]cすることができ[Char]

最初のタイプは固定ですが、会社はString、モデルは、String年はIntです。

Carの例では、ポリモーフィズムを使用することの重要性を示さない場合があります。しかし、あなたのデータがリストタイプであると想像してください。リストに含めることができるString, Char, Int ...これらの状況では、データを定義する2番目の方法が必要になります。

3番目の方法については、前のタイプに合わせる必要はないと思います。Haskellでデータを定義する方法の1つにすぎません。

これは、初心者としての私の謙虚な意見です。

ところで:あなたはあなたの脳をよく訓練し、これに慣れていることを確認してください。後でモナドを理解するための鍵です。


1

タイプについて:最初のケースでは、タイプString(会社とモデル)とInt年を設定します。2番目のケースでは、より一般的です。abおよびc実施例1と全く同じタイプであってもよく、又は完全に異なるもの。たとえば、整数ではなく文字列として年を指定すると便利な場合があります。また、必要に応じて、Colorタイプを使用することもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.