在一个 data
声明中,类型构造器 是等号左边的部分,而 数据构造器 则是等号右边的部分。当需要一个类型时,我们使用类型构造器,而当需要一个值时,我们使用数据构造器。
数据构造器
为了简单起见,我们可以举一个代表颜色的类型的例子。
data Colour = Red | Green | Blue
这里,我们有三个数据构造函数。 Color
是一种类型,Green
是一个构造函数,它包含了一种 Color
类型的值。同样地,Red
和 Blue
都是构造函数,用于构造 Color
类型的值。不过我们可以想象让它更加丰富多彩!
data Colour = RGB Int Int Int
我们仍然只有类型 Colour
,但是 RGB
不是一个值 - 它是一个接受三个 Ints 并 返回 值的函数!RGB
的类型为
RGB :: Int -> Int -> Int -> Colour
RGB
是一个数据构造函数,它接受一些值作为参数,然后使用这些值构造一个新值。如果您有过面向对象编程的经验,应该会认识到这一点。在面向对象编程中,构造函数也接受一些值作为参数并返回一个新值!
在这种情况下,如果我们将RGB
应用于三个值,我们将得到一个颜色值!
Prelude> RGB 12 92 27
我们已经通过应用数据构造器构建了一个颜色
类型的值。数据构造器要么包含像变量一样的值,要么以其他值作为其参数并创建一个新的值。如果您之前有编程经验,这个概念对您来说应该不会很陌生。
中场休息
如果您想构建一个二叉树来存储字符串
,您可以考虑执行以下操作:
data SBTree = Leaf String
| Branch String SBTree SBTree
这里展示的是一个名为SBTree
的类型,它包含两个数据构造函数。换句话说,有两个函数(即Leaf
和Branch
)会构造SBTree
类型的值。如果您不熟悉二叉树的工作原理,请耐心等待。实际上,您并不需要知道二叉树如何工作,只需知道它以某种方式存储String
。
此外,我们还可以看到两个数据构造函数都需要一个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)可以被赋值给程序中的值,而类型构造函数则需要提供一个类型才能赋值给某个值。一个值永远不能是"list"类型,因为它必须是"something的list"类型。同样地,一个值永远不能是"binary tree"类型,因为它必须是"存储something的binary tree"类型。
如果我们将Bool作为BTree的参数传递进去,它将返回类型BTree Bool,它是一个存储Bools的二叉树。替换每个类型变量a为类型Bool,你就会发现它是正确的。
如果你愿意,你可以将BTree视为带有kind的函数。
BTree :: * -> *
Kinds类似于类型——
*
表示具体类型,因此我们说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
的列表 - 如果尝试执行此操作,则会出现错误。
[] :: [Maybe]
如果你创建一个Maybe
类型的列表,你会得到一个错误。然而,你可以创建一个Maybe Int
或者Maybe a
的列表。这是因为Maybe
是一种类型构造函数,但是列表需要包含具体类型的值。Maybe Int
和Maybe a
是具体的类型(或者说是调用返回具体类型的类型构造函数)。
Car
既是类型构造函数(在等号左侧)又是数据构造函数(在右侧)。在第一个示例中,Car
类型构造函数不带参数,在第二个示例中需要三个参数。在两个示例中,Car
数据构造函数都需要三个参数(但在一个示例中这些参数的类型是固定的,在另一个示例中则是参数化的)。 - sshineCar :: String -> String -> Int -> Car
)来构建类型为Car
的数据。第二个是使用一个数据构造器(Car :: a -> b -> c -> Car a b c
)来构建类型为Car a b c
的数据。 - Will Ness