Haskell类型与数据构造器
我正在学习learnyouahaskell.com的Haskell。 我无法理解类型构造函数和数据构造函数。 例如,我不太了解这个之间的区别:
data Car = Car { company :: String
, model :: String
, year :: Int
} deriving (Show)
和这个:
data Car a b c = Car { company :: a
, model :: b
, year :: c
} deriving (Show)
我明白,第一个只是使用一个构造函数( Car
)来构建Car
类型的数据。 我真的不明白第二个。
另外,数据类型如何定义如下:
data Color = Blue | Green | Red
适合所有这一切?
据我所知,第三个例子( Color
)是一种可以处于三种状态的类型: Blue
, Green
或Red
。 但是这与我理解前两个例子的冲突:是否Car
类型只能处于一种状态, Car
可以采用各种参数来构建? 如果是这样,第二个例子如何适用?
基本上,我正在寻找一个统一上述三个代码示例/结构的解释。
在data
声明中,类型构造函数是等号左侧的东西。 数据构造函数是等号右边的东西。 您可以在预期使用类型的地方使用类型构造函数,并且在需要使用值的地方使用数据构造函数。
数据构造函数
为了简单起见,我们可以从代表颜色的类型开始。
data Colour = Red | Green | Blue
在这里,我们有三个数据构造函数。 Colour
是一种类型, Green
是一个构造函数,它包含Colour
类型的值。 同样, Red
和Blue
都是构造类型Colour
值的构造函数。 尽管我们可以想象它们的存在!
data Colour = RGB Int Int Int
我们仍然只是Colour
类型,但RGB
不是一个值 - 它是一个取三个Ints并返回一个值的函数! RGB
有这种类型
RGB :: Int -> Int -> Int -> Colour
RGB
是一个数据构造函数,它是一个将一些值作为参数的函数,然后使用它们构造一个新值。 如果你已经完成了任何面向对象的编程,你应该认识到这一点。 在OOP中,构造函数也将一些值作为参数并返回一个新的值!
在这种情况下,如果我们将RGB
应用于三个值,我们会得到一个颜色值!
Prelude> RGB 12 92 27
#0c5c1b
我们通过应用数据构造函数构造了Colour
类型的值。 一个数据构造函数或者包含一个像变量那样的值,或者将其他值作为它的参数并创建一个新值。 如果你已经完成了以前的编程,这个概念对你来说应该不是很奇怪。
幕间休息
如果你想构建一个二叉树来存储String
,你可以想象做类似的事情
data SBTree = Leaf String
| Branch String SBTree SBTree
我们在这里看到的是一个包含两个数据构造函数的SBTree
类型。 换句话说,有两个函数(即Leaf
和Branch
)将构造SBTree
类型的值。 如果你不熟悉二叉树的工作方式,那就挂在那里。 你实际上并不需要知道二叉树是如何工作的,只是它以某种方式存储String
。
我们还看到,两个数据构造函数都带有一个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
)之间的区别,它是一种可以分配给程序中某个值的类型,还有一个类型构造函数,键入可以分配给一个值。 值永远不能是“列表”类型,因为它需要成为“列表”。 本着同样的精神,一个值永远不能是“二叉树”类型,因为它需要是一个“二叉树存储”。
如果我们传入Bool
作为BTree
的参数,它将返回类型BTree Bool
,它是一个存储Bool
s的二叉树。 用类型Bool
替换每个出现的类型变量a
,并且你可以亲自看到它是如何的。
如果你愿意,你可以将BTree
视为一种功能
BTree :: * -> *
种类有点像类型 - *
表示具体类型,所以我们说BTree
是从具体类型到具体类型。
包起来
回到这里一会儿,注意相似之处。
数据构造函数是一个“函数”,它取0或更多的值,并给你一个新的值。
一个类型构造函数是一个“函数”,它需要0个或更多的类型,并给你一个新的类型。
具有参数的数据构造函数在我们的值有轻微变化的情况下很酷 - 我们将这些变量放在参数中,并让创建值的人决定要输入什么参数。同样,带参数的类型构造函数很酷如果我们想要我们的类型略有变化! 我们把这些变化作为参数,让创建类型的人决定他们将要投入什么论点。
案例研究
作为这里的主场,我们可以考虑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
a列表。 这是因为Maybe
是一个类型构造函数,但是一个列表需要包含具体类型的值。 Maybe Int
和Maybe a
是具体类型(或者如果你想要的话,调用类型构造函数返回具体类型。)
Haskell具有代数数据类型,其他语言很少。 这可能会让你感到困惑。
在其他语言中,您通常可以创建一个“记录”,“结构”或类似的文件,其中包含一堆用于存储各种不同类型数据的命名字段。 你有时也可以做一个“枚举”,它有一小组固定的可能值(例如你的Red
, Green
和Blue
)。
在Haskell中,您可以同时组合这两者。 奇怪,但真实!
为什么它被称为“代数”? 那么,书呆子就会谈论“总和类型”和“产品类型”。 例如:
data Eg1 = One Int | Two String
一个Eg1
值基本上是一个整数或一个字符串。 所以所有可能的Eg1
值的集合就是所有可能的整数值集合和所有可能的字符串值的“总和”。 因此,书呆子将Eg1
称为“和类型”。 另一方面:
data Eg2 = Pair Int String
每个Eg2
值都由一个整数和一个字符串组成。 因此,所有可能的Eg2
值的集合是所有整数集合和所有字符串集合的笛卡尔乘积。 这两套“相乘”在一起,所以这是一个“产品类型”。
Haskell的代数类型是产品类型的总和类型。 您给构造函数提供了多个字段来创建产品类型,并且您有多个构造函数来产生总和(产品)。
举个例子,为什么这可能是有用的,假设你有一些输出数据为XML或JSON的东西,它需要一个配置记录 - 但显然,XML和JSON的配置设置是完全不同的。 所以你可能会这样做:
data Config = XML_Config {...} | JSON_Config {...}
(很明显,在那里有一些合适的字段。)你不能在正常的编程语言中做这样的事情,这就是为什么大多数人不习惯它。
从最简单的案例开始:
data Color = Blue | Green | Red
这定义了一个“类型构造函数” Color
,它没有参数 - 它有三个“数据构造函数”, Blue
, Green
和Red
。 数据构造函数都没有任何参数。 这意味着有三种Color
: Blue
, Green
和Red
。
当您需要创建某种类型的值时,会使用数据构造函数。 喜欢:
myFavoriteColor :: Color
myFavoriteColor = Green
使用Green
数据构造函数创建myFavoriteColor
值 - 而myFavoriteColor
将是Color
类型,因为这是数据构造函数生成的值的类型。
当需要创建某种类型的类型时使用类型构造函数。 编写签名时通常是这种情况:
isFavoriteColor :: Color -> Bool
在这种情况下,您正在调用Color
类型构造函数(不带参数)。
还在我这儿?
现在,想象你不仅想要创建红色/绿色/蓝色值,而且还想指定一个“强度”。 就像一个介于0和256之间的值。你可以通过为每个数据构造函数添加一个参数来实现,所以你最终得到:
data Color = Blue Int | Green Int | Red Int
现在,三个数据构造函数中的每一个都接受一个Int
类型的参数。 类型构造函数( Color
)仍然没有任何参数。 所以,我最喜欢的颜色是深绿色,我可以写
myFavoriteColor :: Color
myFavoriteColor = Green 50
再次,它调用Green
数据构造函数,并获取Color
类型的值。
想象一下,如果你不想指定人们如何表达颜色的强度。 有些人可能需要像我们刚才那样的数值。 其他人可能会很好,只是布尔值表示“明亮”或“不那么明亮”。 解决这个问题的方法是不在数据构造函数中对Int
进行硬编码,而是使用一个类型变量:
data Color a = Blue a | Green a | Red a
现在,我们的类型构造函数有一个参数(另一种类型,我们只需要调用a
!),所有的数据构造将采取一个参数(值!)这种类型的a
。 所以你可以有
myFavoriteColor :: Color Bool
myFavoriteColor = Green False
要么
myFavoriteColor :: Color Int
myFavoriteColor = Green 50
请注意,我们如何使用参数(另一种类型)调用Color
类型构造函数来获取将由数据构造函数返回的“有效”类型。 这触及了您可能想要通过一杯咖啡或两杯咖啡阅读的种类的概念。
现在我们计算出数据构造函数和类型构造函数是什么,以及数据构造函数如何将其他值作为参数,而类型构造函数可以将其他类型作为参数。 HTH。
链接地址: http://www.djcxy.com/p/80857.html