在data
声明中,类型构造函数是等号左侧的东西。数据构造函数是等号右侧的东西。在需要类型的地方使用类型构造函数,在需要值的地方使用数据构造函数。
数据构造函数
为简单起见,我们可以从表示颜色的类型的示例开始。
data Colour = Red | Green | Blue
在这里,我们有三个数据构造函数。Colour
是一个类型,并且Green
是一个包含 type 值的构造函数Colour
。同样,Red
andBlue
都是构造类型值的构造函数Colour
。不过,我们可以想象给它加香料!
data Colour = RGB Int Int Int
我们仍然只有 type Colour
,但RGB
不是 value ——它是一个接受三个 Int 并返回一个值的函数!RGB
有类型
RGB :: Int -> Int -> Int -> Colour
RGB
是一个数据构造函数,它是将一些值作为其参数的函数,然后使用这些值构造一个新值。如果你做过任何面向对象的编程,你应该认识到这一点。在 OOP 中,构造函数也将一些值作为参数并返回一个新值!
在这种情况下,如果我们应用RGB
三个值,我们会得到一个颜色值!
Prelude> RGB 12 92 27
#0c5c1b
我们通过应用数据构造函数构造了一个类型的值。Colour
数据构造函数要么像变量一样包含一个值,要么将其他值作为其参数并创建一个新值。如果你以前做过编程,这个概念对你来说应该不会很陌生。
中场休息
如果你想构建一个二叉树来存储String
s,你可以想象做类似的事情
data SBTree = Leaf String
| Branch String SBTree SBTree
我们在这里看到的是一个SBTree
包含两个数据构造函数的类型。换句话说,有两个函数(即Leaf
和Branch
)将构造该SBTree
类型的值。如果您不熟悉二叉树的工作原理,请坚持下去。您实际上不需要知道二叉树是如何工作的,只需要知道二叉树String
以某种方式存储 s 即可。
我们还看到两个数据构造函数都带有一个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
已经变成了一个函数。它接受一个类型作为参数,并返回一个新类型。
这里重要的是要考虑具体类型(示例包括Int
和赋值给一个值。值永远不能是“列表”类型,因为它必须是“某物的列表”。本着同样的精神,一个值永远不能是“二叉树”类型,因为它需要是一个“存储某些东西的二叉树”。[Char]
Maybe Bool
例如,如果我们将 sBool
作为参数传入BTree
,它会返回 type ,它是存储sBTree Bool
的二叉树。Bool
用 type 替换每次出现的 type 变量a
,Bool
你可以自己看看它是如何正确的。
如果您愿意,您可以将BTree
其视为带有kind的函数
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
接受一个具体类型并返回一个具体类型。
再次!具体类型和类型构造函数之间的区别。您无法创建Maybe
s 列表 - 如果您尝试执行
[] :: [Maybe]
你会得到一个错误。但是,您可以创建Maybe Int
或的列表Maybe a
。这是因为Maybe
它是一个类型构造函数,但列表需要包含具体类型的值。Maybe Int
并且Maybe a
是具体类型(或者,如果您愿意,可以调用返回具体类型的类型构造函数。)