私はHaskellの新参者で、現在RealWorldHaskellを利用しています。この本は、型コンストラクターは型シグネチャーでのみ使用され、値コンストラクターは実際のコードで使用されると述べています。また、両方の名前が互いに独立していることを示す宣言の例も示しています。実際のコードで使用されているコンストラクターが1つだけなのに、そもそも2つのコンストラクターが必要なのはなぜですか。実際のコードでは型コンストラクターを使用しないので、型コンストラクターはどのような目的に役立ちますか?
3 に答える
たぶん、名前は少し誤解を招く可能性があります。型コンストラクターは、宣言する型の名前を表します。それらは値ではなく型を構築するため、そのように呼び出されます。実際、(おそらく)型変数でパラメーター化されているため、型のファミリを定義します。これらは、C++ のテンプレートや Java のジェネリックのように機能します。ではdata MyType a b = Constr a b
、MyType は 2 つの型を受け取り、新しい型を構築する型a
コンストラクタb
です(MyType a b)
。
値コンストラクターは、他の (オブジェクト指向) 言語で「コンストラクター」と呼ぶ唯一の部分です。これは、その型の値を構築するために必要なためです。したがって、前の例で値コンストラクターをConstr :: a -> b -> MyType a b
使用すると、値を作成できますConstr "abc" 'd' :: MyType [Char] Char
。
「実際に実行されるのはオブジェクトだけなのに、どうしてクラスやオブジェクトが必要なの?」と言っているようなものです。
2 種類のコンストラクターは、異なる仕事をします。型コンストラクターは、型シグニチャーに入ります。値のコンストラクターは、実行可能なコードに入ります。
最も単純なケースでは、型「コンストラクター」は単なる型名です。最も単純なケースでは、型には値コンストラクターが 1 つしかありません。だからあなたは次のようなものになります
data Point = Point Int Int
あなたは自分自身にこう言うかもしれませんPoint
。
しかし、ここで、それほど単純ではない例を考えてみましょう:
data Tree x = Leaf x | Branch (Tree x) (Tree x)
ここTree
に型コンストラクターがあります。型引数を与えると、型が「構築」されます。Tree Int
あるタイプもそうですTree String
し、別のタイプもそうです。(C++ のテンプレートや、Java や Eiffel のジェネリックのように。)
一方、Leaf
は値コンストラクターです。値を指定すると、それから 1 ノード ツリーが作成されます。値も値もそうLeaf 5
です。Tree Int
Leaf "banana"
Tree String
についても同様ですBranch
。2 つのツリー値を取り、それらのツリーを子として持つツリー ノードを構築します。たとえば、Branch (Leaf 2) (Leaf 7)
はTree Int
値です。
タイプと値について直感的に理解するための便利な方法の 1 つは、前者がコンパイル時の値であり、後者が実行時の値であるということです。言い換えれば、型コンストラクターは、コンパイル時にプログラムを型付けすることのみを目的とした、一連の Haskell 型の値のコンストラクターです。これは、実行時に型を構築できず、コンパイル時に値を構築できないことも意味します。
したがって、型の値に基づいて実行時に明示的に分岐することはできないため (型クラスを使用して暗黙的に分岐することはできますが)、型コンストラクターは実行時のオブジェクトとしてはまったく役に立たず、多くの場合、最終的なバイナリにはまったく存在しません。逆に、値コンストラクターは実行時に型のセットで値を構築できるため、コンパイル時のオブジェクトとしてはまったく役に立ちません。
この単純なプロパティにより、Type コンストラクターと Value コンストラクターは明確に名前を共有できます。