我是 Haskell 的新手,目前正在使用 Real World Haskell。书中说类型构造函数仅用于类型签名,而值构造函数用于实际代码。它还提供了一个声明示例,以表明两者的名称是相互独立的。如果在实际代码中只使用其中一个,为什么首先需要两个构造函数?既然我们不会在实际代码中使用类型构造函数,那么类型构造函数有什么作用呢?
3 回答
也许这些名字有点误导。类型构造函数表示您要声明的类型的名称。之所以这样称呼它们,是因为它们构建类型,而不是值:实际上,(可能)在类型变量上被参数化,它们定义了一系列类型。它们的行为类似于 C++ 的模板和 Java 的泛型。其中data MyType a b = Constr a b
,MyType 是一个类型构造函数,它采用两种类型a
并b
构建一个新类型(MyType a b)
。
值构造函数是在其他(面向对象的)语言中唯一可以称为“构造函数”的部分,因为您需要它来为该类型构建值。所以,在前面的例子中,如果你使用 value 构造函数Constr :: a -> b -> MyType a b
,你可以构建一个 value Constr "abc" 'd' :: MyType [Char] Char
。
这有点像说“如果对象是唯一实际运行的东西,为什么我们需要类和对象?”
这两种构造函数做不同的工作。类型构造函数进入类型签名。值构造函数进入可运行代码。
在最简单的情况下,类型“构造函数”只是一个类型名称。在最简单的情况下,一个类型只有一个值构造函数。所以你最终会得到类似的东西
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
是一个值构造函数。给定一个值,它会从中生成一棵单节点树。所以Leaf 5
是一个Tree Int
值,Leaf "banana"
是一个Tree String
值,等等。
同样对于Branch
。它采用两个树值并以这些树为子节点构造一个树节点。例如,Branch (Leaf 2) (Leaf 7)
是一个Tree Int
值。
获得关于类型和值的直觉的一种方便方法是,前者是编译时值,而后者是运行时值。换句话说,类型构造函数是 Haskell 类型集合中值的构造函数,其唯一目的是在编译时键入程序。这也意味着您不能在运行时构造类型,也不能在编译时构造值。
因此,因为您不能在运行时基于类型值显式分支(尽管您可以隐式使用类型类),所以类型构造函数作为运行时对象完全没有用,并且在许多情况下在最终二进制文件中完全不存在。相反,因为值构造函数允许在运行时在其类型集中构造值,所以它们作为编译时对象完全没有用。
由于这个简单的属性,类型构造函数和值构造函数可以明确地共享名称。