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 )是一种可以处于三种状态的类型: BlueGreenRed 。 但是这与我理解前两个例子的冲突:是否Car类型只能处于一种状态, Car可以采用各种参数来构建? 如果是这样,第二个例子如何适用?

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


data声明中,类型构造函数是等号左侧的东西。 数据构造函数是等号右边的东西。 您可以在预期使用类型的地方使用类型构造函数,并且在需要使用值的地方使用数据构造函数。

数据构造函数

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

data Colour = Red | Green | Blue

在这里,我们有三个数据构造函数。 Colour是一种类型, Green是一个构造函数,它包含Colour类型的值。 同样, RedBlue都是构造类型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类型。 换句话说,有两个函数(即LeafBranch )将构造SBTree类型的值。 如果你不熟悉二叉树的工作方式,那就挂在那里。 你实际上并不需要知道二叉树是如何工作的,只是它以某种方式存储String

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

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

data BBTree = Leaf Bool
            | Branch Bool BBTree BBTree

类型构造函数

SBTreeBBTree都是类型构造函数。 但是有一个明显的问题。 你看到他们有多相似吗? 这是您确实需要某个参数的标志。

所以我们可以这样做:

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


    Haskell具有代数数据类型,其他语言很少。 这可能会让你感到困惑。

    在其他语言中,您通常可以创建一个“记录”,“结构”或类似的文件,其中包含一堆用于存储各种不同类型数据的命名字段。 你有时也可以做一个“枚举”,它有一小组固定的可能值(例如你的RedGreenBlue )。

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

    当您需要创建某种类型的值时,会使用数据构造函数。 喜欢:

    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

    上一篇: Haskell Type vs Data Constructor

    下一篇: Why not be dependently typed?