Haskell类型与数据构造器 [英] Haskell Type vs Data Constructor

查看:121
本文介绍了Haskell类型与数据构造器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我从 learnyouahaskell.com 学习Haskell。我无法理解类型构造函数和数据构造函数。举例来说,我并没有真正了解这个区别:

  data Car = Car {company :: String 
,model :: String
,year :: Int
}派生(显示)

以及:

  data Car abc = Car {company :: a 
,model :: b
,year :: c
}派生(显示)

据我所知,首先简单地使用一个构造函数( Car )来构建类型为 Car 的数据。

另外,数据类型如何定义如下:

  data Color = Blue |绿色|红色

是否适合所有这些?

据我所知,第三个例子( Color )是一种可以处于三种状态的类型: Blue 绿色红色。但这与我理解前两个例子的冲突:它是 Car 类型只能处于一种状态, Car ,它可以采取各种参数来建立?如果是这样,第二个例子如何适用?



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

解决方案在 data 声明中,类型构造函数是事物在等号的左侧。 数据构造函数是等号右边的东西。您可以使用类型为构造函数的类型,并且您可以使用数据构造函数,其中需要值。

数据构造函数



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

  data颜色=红色|绿色|蓝色

在这里,我们有三个数据构造函数。 Color 是一个类型, Green 是一个构造函数,它包含一个类型为 Color 。类似地, Red Blue 都是构造函数,它们构造类型为 Color 。但是我们可以想象它会让它惊艳!

  data Color = RGB Int Int Int 

我们仍然只有 Color 类型,但 RGB 不是一个值 - 它是一个函数,需要三个Ints并且返回一个值! RGB 具有类型

  RGB :: Int  - > Int  - > Int  - >颜色

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



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

  Prelude> RGB 12 92 27 
#0c5c1b

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



中场休息



如果你想构建一个二叉树来存储 String s,你可以想象做一些像

  data SBTree = Leaf String 
|分支字符串SBTree SBTree

我们在这里看到的是类型 SBTree 。换句话说,有两个函数(即 Leaf Branch ),它们将构造 SBTree 类型。如果你不熟悉二叉树的工作方式,那就挂在那里。你实际上并不需要知道二叉树是如何工作的,只是这个以某种方式存储 String s。



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



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

  data BBTree = Leaf Bool 
| Branch Bool BBTree BBTree



类型构造函数



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



所以我们可以这样做:

 数据BTree a = Leaf a 
|分支a(BTree a)(BTree a)

现在我们介绍一个 em> a 作为类型构造函数的参数。在这个声明中, BTree 已经成为函数。它采用类型作为参数,并返回一个新的类型


这里需要考虑具体类型之间的区别(例子包括 Int [Char] 可能是Bool ),它是一种可以分配给程序中某个值的类型,以及一个您需要的类型构造函数给一个类型提供一个可以赋值的类型。值永远不能是list类型,因为它需要成为list of something 。本着同样的精神,一个值永远不能是二叉树类型,因为它需要是一个二叉树存储某物


如果我们传入 Bool 作为 BTree 的参数,它返回类型为 BTree Bool ,这是一个存储 Bool s的二叉树。用类型 Bool 替换每一个出现的类型变量 a ,你可以看到它是如此。 / p>

如果您愿意,您可以将 BTree 作为类型的函数

  BTree :: *  - > * 

种类有点像类型 - * 表示一个具体的类型,所以我们说 BTree 是从一个具体类型到一个具体类型。



包装

退一步,注意相似之处。 p> 数据构造函数是一个函数,它接受0个或更多并为您返回一个新值。 类型构造函数是一个函数,它接受0个或更多类型并为您提供新类型。


$ b $ p
$ b

如果我们想要稍微改变我们的值,那么带参数的数据构造函数是很酷的 - 我们把这些变量放在参数中,让创建值的人决定什么他们将要放入的参数。同样,如果我们想要在我们的类型中稍微变化,那么带参数的类型构造函数就很酷!我们把这些变化作为参数,让创建类型的人决定他们将要投入什么论点。

案例研究



在这里,我们可以考虑也许一个类型。它的定义是

  data也许a = Nothing 
|只需

这里, Maybe 是一种类型构造函数返回一个具体类型。 Just 是一个返回值的数据构造函数。 Nothing 是一个包含值的数据构造函数。如果我们看看 Just 的类型,我们可以看到

  Just :: a  - >也许是

换句话说, Just 需要类型 a 的值,并返回类型的值。也许a 。如果我们看看 Maybe 类型,我们可以看到

 也许:: *  - > * 

换句话说,也许需要一个具体类型并返回一个具体类型。



再一次!具体类型和类型构造函数之间的区别。你不能创建可能 s的列表 - 如果你试图执行

  [] :: [也许] 

您会收到错误消息。但是,您可以创建 Maybe Int 也许是的列表。这是因为 Maybe 是一个类型构造函数,但是列表需要包含具体类型的值。 可能是Int 也许一个是具体类型(或者如果需要的话,可以调用返回具体类型的构造函数。 )

I am learning Haskell from learnyouahaskell.com. I am having trouble understanding type constructors and data constructors. For example, I don't really understand the difference between this:

data Car = Car { company :: String  
               , model :: String  
               , year :: Int  
               } deriving (Show) 

and this:

data Car a b c = Car { company :: a  
                     , model :: b  
                     , year :: c   
                     } deriving (Show)  

I understand that the first is simply using one constructor (Car) to built data of type Car. I don't really understand the second one.

Also, how do data types defined like this:

data Color = Blue | Green | Red

fit into all of this?

From what I understand, the third example (Color) is a type which can be in three states: Blue, Green or Red. But that conflicts with how I understand the first two examples: is it that the type Car can only be in one state, Car, which can take various parameters to build? If so, how does the second example fit in?

Essentially, I am looking for an explanation that unifies the above three code examples/constructs.

解决方案

In a data declaration, a type constructor is the thing on the left hand side of the equals sign. The data constructor(s) are the things on the right hand side of the equals sign. You use type constructors where a type is expected, and you use data constructors where a value is expected.

Data constructors

To make things simple, we can start with an example of a type that represents a colour.

data Colour = Red | Green | Blue

Here, we have three data constructors. Colour is a type, and Green is a constructor that contains a value of type Colour. Similarly, Red and Blue are both constructors that construct values of type Colour. We could imagine spicing it up though!

data Colour = RGB Int Int Int

We still have just the type Colour, but RGB is not a value – it's a function taking three Ints and returning a value! RGB has the type

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

RGB is a data constructor that is a function taking some values as its arguments, and then uses those to construct a new value. If you have done any object-oriented programming, you should recognise this. In OOP, constructors also take some values as arguments and return a new value!

In this case, if we apply RGB to three values, we get a colour value!

Prelude> RGB 12 92 27
#0c5c1b

We have constructed a value of type Colour by applying the data constructor. A data constructor either contains a value like a variable would, or takes other values as its argument and creates a new value. If you have done previous programming, this concept shouldn't be very strange to you.

Intermission

If you'd want to construct a binary tree to store Strings, you could imagine doing something like

data SBTree = Leaf String
            | Branch String SBTree SBTree

What we see here is a type SBTree that contains two data constructors. In other words, there are two functions (namely Leaf and Branch) that will construct values of the SBTree type. If you're not familiar with how binary trees work, just hang in there. You don't actually need to know how binary trees work, only that this one stores Strings in some way.

We also see that both data constructors take a String argument – this is the String they are going to store in the tree.

But! What if we also wanted to be able to store Bool, we'd have to create a new binary tree. It could look something like this:

data BBTree = Leaf Bool
            | Branch Bool BBTree BBTree

Type constructors

Both SBTree and BBTree are type constructors. But there's a glaring problem. Do you see how similar they are? That's a sign that you really want a parameter somewhere.

So we can do this:

data BTree a = Leaf a
             | Branch a (BTree a) (BTree a)

Now we introduce a type variable a as a parameter to the type constructor. In this declaration, BTree has become a function. It takes a type as its argument and it returns a new type.

It is important here to consider the difference between a concrete type (examples include Int, [Char] and Maybe Bool) which is a type that can be assigned to a value in your program, and a type constructor function which you need to feed a type to be able to be assigned to a value. A value can never be of type "list", because it needs to be a "list of something". In the same spirit, a value can never be of type "binary tree", because it needs to be a "binary tree storing something".

If we pass in, say, Bool as an argument to BTree, it returns the type BTree Bool, which is a binary tree that stores Bools. Replace every occurrence of the type variable a with the type Bool, and you can see for yourself how it's true.

If you want to, you can view BTree as a function with the kind

BTree :: * -> *

Kinds are somewhat like types – the * indicates a concrete type, so we say BTree is from a concrete type to a concrete type.

Wrapping up

Step back here a moment and take note of the similarities.

  • A data constructor is a "function" that takes 0 or more values and gives you back a new value.

  • A type constructor is a "function" that takes 0 or more types and gives you back a new type.

Data constructors with parameters are cool if we want slight variations in our values – we put those variations in parameters and let the guy who creates the value decide what arguments they are going to put in. In the same sense, type constructors with parameters are cool if we want slight variations in our types! We put those variations as parameters and let the guy who creates the type decide what arguments they are going to put in.

A case study

As the home stretch here, we can consider the Maybe a type. Its definition is

data Maybe a = Nothing
             | Just a

Here, Maybe is a type constructor that returns a concrete type. Just is a data constructor that returns a value. Nothing is a data constructor that contains a value. If we look at the type of Just, we see that

Just :: a -> Maybe a

In other words, Just takes a value of type a and returns a value of type Maybe a. If we look at the kind of Maybe, we see that

Maybe :: * -> *

In other words, Maybe takes a concrete type and returns a concrete type.

Once again! The difference between a concrete type and a type constructor function. You cannot create a list of Maybes - if you try to execute

[] :: [Maybe]

you'll get an error. You can however create a list of Maybe Int, or Maybe a. That's because Maybe is a type constructor function, but a list needs to contain values of a concrete type. Maybe Int and Maybe a are concrete types (or if you want, calls to type constructor functions that return concrete types.)

这篇关于Haskell类型与数据构造器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆