Haskell类型与数据构造器 [英] Haskell Type vs Data Constructor
问题描述
我从 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
,它可以采取各种参数来建立?如果是这样,第二个例子如何适用?
基本上,我正在寻找一个解释来统一上述三个代码示例/结构。 $ b
data
声明中,类型构造函数是事物在等号的左侧。 数据构造函数是等号右边的东西。您可以使用类型为构造函数的类型,并且您可以使用数据构造函数,其中需要值。 数据构造函数
为了简单起见,我们可以从一个代表颜色的类型开始。
data颜色=红色|绿色|蓝色
在这里,我们有三个数据构造函数。 Color 是一个类型,
Green
是一个构造函数,它包含一个类型为 Color
。类似地, Red
和 Blue
都是构造函数,它们构造类型为 Color
。但是我们可以想象它会让它惊艳!
data Color = RGB Int Int Int
我们仍然只有 RGB
具有类型
RGB :: Int - > Int - > Int - >颜色
RGB
是一个数据构造函数,它是一个以值为参数的函数,然后使用它们构造一个新值。如果你已经完成了任何面向对象的编程,你应该认识到这一点。在OOP中,构造函数也将一些值作为参数并返回一个新值!
在这种情况下,如果我们应用 RGB
三个值,我们得到一个颜色值!
Prelude> RGB 12 92 27
#0c5c1b
我们有构造了一个值类型 Color
通过应用数据构造函数。一个数据构造函数或者包含一个像变量那样的值,或者将其他值作为它的参数并创建一个新的值。如果你已经完成了以前的编程,这个概念对你来说应该不是很奇怪。
中场休息
如果你想构建一个二叉树来存储 String
s,你可以想象做一些像
data SBTree = Leaf String
|分支字符串SBTree SBTree
我们在这里看到的是类型 SBTree $包含两个数据构造函数的c $ c>。换句话说,有两个函数(即
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 String
s, 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 String
s 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]
andMaybe 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 Bool
s. 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 Maybe
s - 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屋!