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 )来构buildCartypes的数据。 我真的不明白第二个。

另外,数据types如何定义如下:

 data Color = Blue | Green | Red 

适合所有这一切? 据我所知,第三个例子( Color )是一种可以有三种状态的types: BlueGreenRed 。 但是,这与我对前两个例子的理解是否相冲突:是否Cartypes只能处于一种状态, Car可以采取各种参数来构build? 如果是这样,第二个例子如何适应?

本质上,我正在寻找一个统一上述三个代码示例/结构的解释。

data声明中, types构造函数是等号左边的东西。 数据构造函数是等号右边的东西。 您使用的types构造函数的types是预期的,并且您使用的数据构造函数的值是预期的。

数据构造函数

为了简单起见,我们可以从一个代表颜色的types开始。

 data Colour = Red | Green | Blue 

在这里,我们有三个数据构造函数。 Colour是一种types, Green是一个构造函数,它包含Colourtypes的值。 同样, RedBlue都是构造typesColour值的构造函数。 虽然我们可以想象它们的精神!

 data Colour = RGB Int Int Int 

我们仍然只是Colourtypes,但是RGB不是一个值 – 它是一个取三个Ints并返回一个值的函数! RGB有这种types

 RGB :: Int -> Int -> Int -> Colour 

RGB是一个数据构造函数,它是以某些作为参数的函数,然后使用这些构造函数构造一个新的值。 如果你已经做了任何面向对象的编程,你应该认识到这一点。 在OOP中,构造函数也将一些值作为参数并返回一个新的值!

在这种情况下,如果我们将RGB应用于三个值,则会得到一个颜色值!

 Prelude> RGB 12 92 27 #0c5c1b 

我们通过应用数据构造函数构造了 Colourtypes的值 。 一个数据构造函数或者像variables一样包含一个值,或者将其他值作为它的参数并创build一个新的 。 如果你已经完成了以前的编程,这个概念对你来说应该不是很奇怪。

幕间rest

如果你想构build一个二叉树来存储String ,你可以想象做类似的事情

 data SBTree = Leaf String | Branch String SBTree SBTree 

我们在这里看到的是一个包含两个数据构造函数的typesSBTree 。 换句话说,有两个函数(即LeafBranch )将构造SBTreetypes的值。 如果你不熟悉二叉树是如何工作的,那么就挂在那里。 你实际上并不需要知道二叉树是如何工作的,只是这个以某种方式存储String

我们还看到,两个数据构造函数都带有一个String参数 – 这是他们要存储在树中的String。

但! 如果我们也希望能够存储Bool ,我们将不得不创build一个新的二叉树。 它可能看起来像这样:

 data BBTree = Leaf Bool | Branch Bool BBTree BBTree 

types构造函数

SBTreeBBTree都是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 Booltypes,它是一个存储Bool的二叉树。 用Booltypesreplace每个出现的typesvariablesa ,你可以自己看看它是如何的。

如果你愿意的话,你可以把BTree看作是一种function

 BTree :: * -> * 

种类有点像types – *表示具体types,所以我们说BTree是从具体types到具体types。

包起来

回到这里一会儿,注意相似之处。

  • 数据构造函数是一个“函数”,它取0或更多的值,并给你一个新的值。

  • 一个types构造函数是一个“函数”,可以使用0个或更多的types,并给你一个新的types。

具有参数的数据构造函数是很酷的,如果我们想要稍微改变我们的值 – 我们把这些variables放在参数中,让创build值的人决定将要放入什么参数。同样的,带参数的types构造函数是很酷的如果我们想在我们的types略有变化! 我们把这些变化作为参数,让创build这个types的人决定他们将要投入什么论点。

案例研究

在这里,我们可以考虑Maybe atypes。 它的定义是

 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 IntMaybe a是具体types(或者如果你想的话,调用types构造函数返回具体types。)

Haskell具有代数数据types ,其他语言很less。 这也许是令你困惑的事情。

在其他语言中,您通常可以创build一个“logging”,“结构”或类似的,它有一堆命名的字段,其中包含各种不同types的数据。 你也可以做一个“枚举”,它有一个(小的)一组固定的可能值(例如你的RedGreenBlue )。

在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 ,它没有参数 – 它有三个“数据构造函数”, BlueGreenRed 。 数据构造函数都没有任何参数。 这意味着有三种ColorBlueGreenRed

当您需要创build某种types的值时,使用数据构造函数。 喜欢:

 myFavoriteColor :: Color myFavoriteColor = Green 

使用Green数据构造函数创build值myFavoriteColormyFavoriteColor将是Colortypes,因为这是数据构造函数生成的值的types。

当需要创build某种types的types时 ,使用types构造函数。 编写签名时通常是这种情况:

 isFavoriteColor :: Color -> Bool 

在这种情况下,您正在调用Colortypes构造函数(不带参数)。

还在我这儿?

现在,想象一下,您不仅要创build红/绿/蓝值,还要指定“强度”。 就像一个介于0和256之间的值。你可以通过为每个数据构造函数添加一个参数来实现,所以你最终得到:

 data Color = Blue Int | Green Int | Red Int 

现在,三个数据构造函数中的每一个都带有一个Inttypes的参数。 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)调用Colortypes构造函数来获取数据构造函数将返回的“有效”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不足以识别公司,但它需要有自己的Companytypes(包含国家,地址,后退帐户等附加数据)。 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年。 在第二种情况下,你是更通用的。 abc可能与第一个例子中的types完全相同, 或者完全不同。 例如,将年份设置为string而不是整数可能会很有用。 如果你想,你甚至可以使用你的Colortypes。