Haskelltypes与数据构造器
所以我正在学习learnyouahaskell.com的 Haskell,而我无法理解types构造函数和数据构造函数。 例如,我不太了解这个之间的区别:
data Car = Car { company :: String , model :: String , year :: Int } deriving (Show)
和这个:
data Car abc = Car { company :: a , model :: b , year :: c } deriving (Show)
我明白,第一个是简单地使用一个构造函数( Car
)来构buildCar
types的数据。 我真的不明白第二个。
另外,数据types如何定义如下:
data Color = Blue | Green | Red
适合所有这一切? 据我所知,第三个例子( Color
)是一种可以有三种状态的types: Blue
, Green
或Red
。 但是,这与我对前两个例子的理解是否相冲突:是否Car
types只能处于一种状态, Car
可以采取各种参数来构build? 如果是这样,第二个例子如何适应?
本质上,我正在寻找一个统一上述三个代码示例/结构的解释。
在data
声明中, types构造函数是等号左边的东西。 数据构造函数是等号右边的东西。 您使用的types构造函数的types是预期的,并且您使用的数据构造函数的值是预期的。
数据构造函数
为了简单起见,我们可以从一个代表颜色的types开始。
data Colour = Red | Green | Blue
在这里,我们有三个数据构造函数。 Colour
是一种types, Green
是一个构造函数,它包含Colour
types的值。 同样, Red
和Blue
都是构造typesColour
值的构造函数。 虽然我们可以想象它们的精神!
data Colour = RGB Int Int Int
我们仍然只是Colour
types,但是RGB
不是一个值 – 它是一个取三个Ints并返回一个值的函数! RGB
有这种types
RGB :: Int -> Int -> Int -> Colour
RGB
是一个数据构造函数,它是以某些值作为参数的函数,然后使用这些构造函数构造一个新的值。 如果你已经做了任何面向对象的编程,你应该认识到这一点。 在OOP中,构造函数也将一些值作为参数并返回一个新的值!
在这种情况下,如果我们将RGB
应用于三个值,则会得到一个颜色值!
Prelude> RGB 12 92 27 #0c5c1b
我们通过应用数据构造函数构造了 Colour
types的值 。 一个数据构造函数或者像variables一样包含一个值,或者将其他值作为它的参数并创build一个新的值 。 如果你已经完成了以前的编程,这个概念对你来说应该不是很奇怪。
幕间rest
如果你想构build一个二叉树来存储String
,你可以想象做类似的事情
data SBTree = Leaf String | Branch String SBTree SBTree
我们在这里看到的是一个包含两个数据构造函数的typesSBTree
。 换句话说,有两个函数(即Leaf
和Branch
)将构造SBTree
types的值。 如果你不熟悉二叉树是如何工作的,那么就挂在那里。 你实际上并不需要知道二叉树是如何工作的,只是这个以某种方式存储String
。
我们还看到,两个数据构造函数都带有一个String
参数 – 这是他们要存储在树中的String。
但! 如果我们也希望能够存储Bool
,我们将不得不创build一个新的二叉树。 它可能看起来像这样:
data BBTree = Leaf Bool | Branch Bool BBTree BBTree
types构造函数
SBTree
和BBTree
都是types构造函数。 但是有一个明显的问题。 你看他们有多相似吗? 这是一个标志,你真的想要一个参数的地方。
所以我们可以做到这一点:
data BTree a = Leaf a | Branch a (BTree a) (BTree a)
现在我们引入一个typesvariables a
作为types构造函数的一个参数。 在这个声明中, BTree
已经成为一个函数。 它以一个types作为参数,并返回一个新的types 。
在这里重要的是要考虑一个具体types (例子包括
Int
,[Char]
和Maybe Bool
)之间的区别,这个types可以被分配给程序中的一个值,还需要一个types构造函数 ,键入可以分配给一个值。 一个值永远不能是“list”types,因为它需要是一个“list of something ”。 本着同样的精神,一个值永远不能是“二叉树”types,因为它需要是一个“二叉树存储”。
如果我们传入Bool
作为BTree
的参数,它将返回BTree Bool
types,它是一个存储Bool
的二叉树。 用Bool
typesreplace每个出现的typesvariablesa
,你可以自己看看它是如何的。
如果你愿意的话,你可以把BTree
看作是一种function
BTree :: * -> *
种类有点像types – *
表示具体types,所以我们说BTree
是从具体types到具体types。
包起来
回到这里一会儿,注意相似之处。
-
数据构造函数是一个“函数”,它取0或更多的值,并给你一个新的值。
-
一个types构造函数是一个“函数”,可以使用0个或更多的types,并给你一个新的types。
具有参数的数据构造函数是很酷的,如果我们想要稍微改变我们的值 – 我们把这些variables放在参数中,让创build值的人决定将要放入什么参数。同样的,带参数的types构造函数是很酷的如果我们想在我们的types略有变化! 我们把这些变化作为参数,让创build这个types的人决定他们将要投入什么论点。
案例研究
在这里,我们可以考虑Maybe a
types。 它的定义是
data Maybe a = Nothing | Just a
在这里, Maybe
是一个返回具体types的types构造函数。 Just
一个返回值的数据构造函数。 Nothing
是包含值的数据构造函数。 如果我们看Just
的types,我们可以看到
Just :: a -> Maybe a
换句话说, Just
一个typesa
a的值并返回一个types为Maybe a
的值。 如果我们看那种“ Maybe
,我们看到了
Maybe :: * -> *
换言之, Maybe
可以采用具体types并返回具体types。
再来一次! 具体types和types构造函数的区别。 你不能创buildMaybe
的列表 – 如果你尝试执行
[] :: [Maybe]
你会得到一个错误。 然而,你可以创build一个Maybe Int
列表,或者Maybe a
。 这是因为Maybe
是一个types构造函数,但是一个列表需要包含具体types的值。 Maybe Int
和Maybe a
是具体types(或者如果你想的话,调用types构造函数返回具体types。)
Haskell具有代数数据types ,其他语言很less。 这也许是令你困惑的事情。
在其他语言中,您通常可以创build一个“logging”,“结构”或类似的,它有一堆命名的字段,其中包含各种不同types的数据。 你也可以做一个“枚举”,它有一个(小的)一组固定的可能值(例如你的Red
, Green
和Blue
)。
在Haskell中,你可以同时把这两个结合起来 。 奇怪,但真实!
为什么叫做“代数”? 那么,书呆子就会谈到“总和types”和“产品types”。 例如:
data Eg1 = One Int | Two String
一个Eg1
值基本上是一个整数或一个string。 因此,所有可能的Eg1
值的集合是所有可能的整数值集合和所有可能的string值的“总和”。 因此,书呆子把Eg1
称为“和types”。 另一方面:
data Eg2 = Pair Int String
每个Eg2
值都由一个整数和一个string组成。 所以所有可能的Eg2
值的集合就是所有整数集合和所有string集合的笛卡尔乘积。 两套“相乘”在一起,所以这是一个“产品types”。
Haskell的代数types是产品types的总和types 。 你给一个构造函数多个字段来产生一个产品types,并且你有多个构造函数来产生总和(产品)。
举个例子,为什么这可能是有用的,假设你有一些输出数据为XML或JSON的东西,它需要一个configurationlogging – 但显然,XML和JSON的configuration设置是完全不同的。 所以你可能会这样做:
data Config = XML_Config {...} | JSON_Config {...}
(显然有一些合适的字段。)在正常的编程语言中,你不能这样做,这就是为什么大多数人不习惯它。
从最简单的情况开始:
data Color = Blue | Green | Red
这定义了一个“types构造函数” Color
,它没有参数 – 它有三个“数据构造函数”, Blue
, Green
和Red
。 数据构造函数都没有任何参数。 这意味着有三种Color
: Blue
, Green
和Red
。
当您需要创build某种types的值时,使用数据构造函数。 喜欢:
myFavoriteColor :: Color myFavoriteColor = Green
使用Green
数据构造函数创build值myFavoriteColor
– myFavoriteColor
将是Color
types,因为这是数据构造函数生成的值的types。
当需要创build某种types的types时 ,使用types构造函数。 编写签名时通常是这种情况:
isFavoriteColor :: Color -> Bool
在这种情况下,您正在调用Color
types构造函数(不带参数)。
还在我这儿?
现在,想象一下,您不仅要创build红/绿/蓝值,还要指定“强度”。 就像一个介于0和256之间的值。你可以通过为每个数据构造函数添加一个参数来实现,所以你最终得到:
data Color = Blue Int | Green Int | Red Int
现在,三个数据构造函数中的每一个都带有一个Int
types的参数。 types构造函数( Color
)仍然不带任何参数。 所以,我最喜欢的颜色是深绿色的,我可以写
myFavoriteColor :: Color myFavoriteColor = Green 50
再次,它调用Green
数据构造函数,我得到一个types的Color
值。
想象一下,如果你不想指定人们如何expression颜色的强度。 有些人可能需要一个像我们刚刚做的数字值。 其他人可能会很好,只是布尔值指示“明亮”或“不那么明亮”。 解决这个问题的方法是不在数据构造函数中对Int
进行硬编码,而是使用一个typesvariables:
data Color a = Blue a | Green a | Red a
现在,我们的types构造函数接受一个参数(我们称之为另一个types!),并且所有的数据构造函数都将带有一个types为a
参数(一个值!)。 所以你可以有
myFavoriteColor :: Color Bool myFavoriteColor = Green False
要么
myFavoriteColor :: Color Int myFavoriteColor = Green 50
请注意,我们如何使用参数(另一种types)调用Color
types构造函数来获取数据构造函数将返回的“有效”types。 这触及了你可能想要读一杯咖啡或两杯咖啡的种类的概念。
现在我们想出了数据构造函数和types构造函数是什么,以及数据构造函数如何将其他值作为参数,而types构造函数可以将其他types作为参数。 HTH。
正如其他人指出的那样,多态在这里并不是那么可怕。 我们来看看另外一个你可能已经很熟悉的例子:
Maybe a = Just a | Nothing
这个types有两个数据构造函数。 Nothing
是无聊的,它不包含任何有用的数据。 另一方面Just
包含一个值 – 无论types可能有。 让我们编写一个使用这种types的函数,例如获取一个Int
列表的头部,如果有的话(我希望你认为这比抛出一个错误更有用):
maybeHead :: [Int] -> Maybe Int maybeHead [] = Nothing maybeHead (x:_) = Just x > maybeHead [1,2,3] -- Just 1 > maybeHead [] -- None
所以在这种情况下, a
是一个Int
,但对任何其他types来说都是一样的。 事实上,你可以使我们的function适用于每种types的列表(即使不改变实现):
maybeHead :: [t] -> Maybe t maybeHead [] = Nothing maybeHead (x:_) = Just x
另一方面,你可以写只接受某种types的Maybe
函数,例如
doubleMaybe :: Maybe Int -> Maybe Int doubleMaybe Just x = Just (2*x) doubleMaybe Nothing= Nothing
所以长话短说,多态性,你给你自己的types的灵活性,与其他不同types的值工作。
在你的例子中,你可能会决定String
不足以识别公司,但它需要有自己的Company
types(包含国家,地址,后退帐户等附加数据)。 Car
第一个实现需要更改为使用Company
而不是String
作为其第一个值。 你的第二个实现就好,你用它作为Car Company String Int
,它会像以前一样工作(当然访问公司数据的function需要改变)。
第二个是“多态”的概念。
abc可以是任何types的。 例如,a可以是[String],b可以是[Int]和c [Char]
而第一个types是固定的:公司是一个string,模型是一个string,年份是国际。
汽车的例子可能不会显示使用多态性的意义。 但是,想象你的数据是列表types。 一个列表可以包含String,Char,Int …在这些情况下,您将需要第二种定义数据的方法。
至于第三种方式,我不认为它需要适应以前的types。 这只是在Haskell中定义数据的另一种方式。
这是我自己作为初学者的愚蠢观点。
顺便说一句:确保你训练你的大脑,并感觉舒适。 这是后来了解Monad的关键。
这是关于types :在第一种情况下,您设置typesString
(公司和模型)和Int
年。 在第二种情况下,你是更通用的。 a
, b
和c
可能与第一个例子中的types完全相同, 或者完全不同。 例如,将年份设置为string而不是整数可能会很有用。 如果你想,你甚至可以使用你的Color
types。