在data 声明中,类型构造函数 是等号左侧的东西。 数据构造函数是等号右边的东西。在需要类型的地方使用类型构造函数,在需要值的地方使用数据构造函数。
数据构造函数
为简单起见,我们可以从表示颜色的类型的示例开始。
data Colour = Red | Green | Blue
这里,我们有三个数据构造函数。 Colour 是一个类型,Green 是一个包含Colour 类型值的构造函数。同样,Red 和 Blue 都是构造 Colour 类型值的构造函数。不过,我们可以想象为它增添趣味!
data Colour = RGB Int Int Int
我们仍然只有Colour 类型,但RGB 不是一个值——它是一个接受三个Int 并返回一个值的函数! RGB 有类型
RGB :: Int -> Int -> Int -> Colour
RGB 是一个数据构造函数,它是一个将一些 值 作为其参数的函数,然后使用这些值构造一个新值。如果你做过任何面向对象的编程,你应该认识到这一点。在 OOP 中,构造函数也将一些值作为参数并返回一个新值!
在这种情况下,如果我们将RGB 应用于三个值,我们会得到一个颜色值!
Prelude> RGB 12 92 27
#0c5c1b
我们通过应用数据构造函数构造了一个Colour 类型的值。数据构造函数要么像变量一样包含一个值,要么将其他值作为其参数并创建一个新的值。如果你以前做过编程,这个概念对你来说应该不会很陌生。
中场休息
如果你想构建一个二叉树来存储Strings,你可以想象做类似的事情
data SBTree = Leaf String
| Branch String SBTree SBTree
我们在这里看到的是一个类型SBTree,它包含两个数据构造函数。换句话说,有两个函数(即Leaf 和Branch)将构造SBTree 类型的值。如果您不熟悉二叉树的工作原理,请坚持下去。您实际上不需要知道二叉树是如何工作的,只需知道二叉树以某种方式存储Strings。
我们还看到,两个数据构造函数都采用String 参数——这是它们要存储在树中的字符串。
但是!如果我们还希望能够存储Bool,我们必须创建一个新的二叉树。它可能看起来像这样:
data BBTree = Leaf Bool
| Branch Bool BBTree BBTree
类型构造函数
SBTree 和 BBTree 都是类型构造函数。但是有一个明显的问题。你看出它们有多相似了吗?这表明您确实需要某个参数。
所以我们可以这样做:
data BTree a = Leaf a
| Branch a (BTree a) (BTree a)
现在我们引入一个类型变量 a 作为类型构造函数的参数。在此声明中,BTree 已成为一个函数。它以 type 作为参数,并返回一个新的 type。
在此考虑具体类型(示例包括Int、[Char] 和Maybe Bool)之间的区别很重要,这是一种可以分配给您的值的类型程序和一个类型构造函数,您需要提供一个类型以便能够分配给一个值。值永远不能是“列表”类型,因为它必须是“列表某物”。本着同样的精神,值永远不能是“二叉树”类型,因为它必须是“存储某物的二叉树”。
如果我们将Bool 作为参数传递给BTree,它会返回BTree Bool 类型,这是一个存储Bools 的二叉树。将类型变量a 的每一次出现都替换为Bool 类型,您可以自己看看它是如何正确的。
如果您愿意,可以将BTree 视为具有种类
的函数
BTree :: * -> *
种类有点像类型——* 表示具体类型,所以我们说BTree 是从具体类型到具体类型。
结束
后退一步,注意相似之处。
如果我们想对我们的值进行细微的变化,带有参数的数据构造函数很酷——我们将这些变化放在参数中,让创建值的人决定他们要放入什么参数。在同样的意义上,类型构造函数带有如果我们想要我们的类型有细微的变化,参数很酷!我们将这些变体作为参数,让创建类型的人决定他们要输入的参数。
案例研究
作为这里的起点,我们可以考虑Maybe a 类型。它的定义是
data Maybe a = Nothing
| Just a
这里,Maybe 是一个返回具体类型的类型构造函数。 Just 是一个返回值的数据构造函数。 Nothing 是一个包含值的数据构造函数。如果我们查看Just 的类型,我们会看到
Just :: a -> Maybe a
换句话说,Just 接受 a 类型的值并返回 Maybe a 类型的值。如果我们查看Maybe 的类型,我们会看到
Maybe :: * -> *
换句话说,Maybe 接受一个具体类型并返回一个具体类型。
再一次!具体类型和类型构造函数之间的区别。您无法创建 Maybes 列表 - 如果您尝试执行
[] :: [Maybe]
你会得到一个错误。但是,您可以创建Maybe Int 或Maybe a 的列表。那是因为Maybe 是一个类型构造函数,但列表需要包含具体类型的值。 Maybe Int 和 Maybe a 是具体类型(或者,如果您愿意,可以调用返回具体类型的类型构造函数。)