Section: 2.2 User-Defined Types

第 2.2 节 用户自定义类型

The type Bool is already defined in the Prelude so there is no need to define it here.

类型 Bool 已经在 Prelude 中定义了,所以这里无需再定义它。

data Color = Red | Green | Blue | Indigo | Violet deriving Show

The deriving Show is necessary if you want to print a Color value.

如果你想打印一个 Color 值,deriving Show 是必需的。

You can now evaluate these expressions.

现在你可以求值这些表达式。

e1 :: Color
e1 = Red
e2 :: [Color]
e2 = [Red,Blue]

It is very important to keep the expression language and the type language in Haskell separated. The data declaration above defines the type constructor Color. This is a nullary constructor: it takes no arguments. Color is found ONLY in the type language - it can not be

part of an expression.  e1 = Color is meaningless.  (Actually, Color could

be both a data constructor and a type constructor but we'll ignore this possibility for now). On the other hand, Red, Blue, and so on are (nullary) data constructors. They can appear in expressions and in patterns (described later). The declaration e1 :: Blue would also be meaningless. Data constructors can be defined ONLY in a data declaration.

把 Haskell 的表达式语言和类型语言严格区分开非常重要。上面的数据声明

定义了类型构造器 Color。这是一个零元构造器:它不接受任何参数。

Color 只存在于类型语言中——它不能是表达式的一部分。e1 = Color 是没有

意义的。(实际上,Color 可以同时是数据构造器和类型构造器,但这个

可能性我们暂时不管。)另一方面,Red、Blue 等是(零元)数据构造器。

它们可以出现在表达式和模式(后文介绍)中。声明 e1 :: Blue 同样没有

意义。数据构造器只能在数据声明中定义。

In the next example, Point is a type constructor and Pt is a data constructor. Point takes one argument and Pt takes two. A data constructor like Pt is really just an ordinary function except that it can be used in a pattern. Type signatures can not be supplied directly for data constructors; their typing is completely defined by the data declaration. However, data constructors have a signature just like any variable:

Pt :: a -> a -> Point a   -- Not valid Haskell syntax

That is, Pt is a function which takes two arguments with the same arbitrary type and returns a value containing the two argument values.

在下一个例子中,Point 是类型构造器,Pt 是数据构造器。Point 接受一个

参数,Pt 接受两个参数。像 Pt 这样的数据构造器实际上就是一个普通函数,

只是它可以用于模式中。不能直接为数据构造器提供类型签名;它们的类型

规则完全由数据声明定义。不过,数据构造器像任何变量一样拥有一个签名:

Pt :: a -> a -> Point a   -- Not valid Haskell syntax

也就是说,Pt 是一个函数,它接受两个具有同一种任意类型的参数,并返回

一个包含这两个参数值的值。

data Point a = Pt a a   deriving Show
e3 :: Point Float
e3 = Pt 2.0 3.0
e4 :: Point Char
e4 = Pt 'a' 'b'
e5 :: Point (Point Int)
e5 = Pt (Pt 1 2) (Pt 3 4)
-- e6 = Pt 'a' True         -- This is a typing error

The individual components of a point do not have names. Let's jump ahead a little so that we can write functions using these data types. Data constructors (Red, Blue, ..., and Pt) can be used in patterns. When more than one equation is used to define a function, pattern matching occurs top down.

点的各个分量没有名字。我们稍微往前跳一点,以便能用这些数据类型编写

函数。数据构造器(Red、Blue……以及 Pt)可以用于模式。当一个函数由

多条方程定义时,模式匹配自上而下进行。

A function to remove red from a list of colors.

一个从颜色列表中去除红色的函数。

removeRed :: [Color] -> [Color]
removeRed [] = []
removeRed (Red:cs) = removeRed cs
removeRed (c:cs) = c : removeRed cs  -- c cannot be Red at this point
e7 :: [Color]
e7 = removeRed [Blue,Red,Green,Red]

Pattern matching is capable of testing equality with a specific color.

模式匹配能够测试是否与某个特定颜色相等。

All equations defining a function must share a common type. A definition such as:

定义一个函数的所有方程必须共享同一个类型。像这样的定义:

foo Red = 1
foo (Pt x y) = x

would result in a type error since the argument to foo cannot be both a Color and a Point. Similarly, the right hand sides must also share a common type; a definition such as

会导致类型错误,因为 foo 的参数不可能既是 Color 又是 Point。类似地,

各条方程的右边也必须共享同一个类型;像这样的定义

foo Red = Blue
foo Blue = Pt Red Red

would also result in a type error.

同样会导致类型错误。

Here are a couple of functions defined on points.

这里有两个定义在点上的函数。

dist :: Point Float -> Point Float -> Float
dist (Pt x1 y1) (Pt x2 y2) = sqrt ((x1-x2)^2 + (y1-y2)^2)
midpoint :: Point Float -> Point Float -> Point Float
midpoint (Pt x1 y1) (Pt x2 y2) = Pt ((x1+x2)/2) ((y1+y2)/2)
p1 :: Point Float
p1 = Pt 1.0 1.0
p2 :: Point Float
p2 = Pt 2.0 2.0
e8 :: Float
e8 = dist p1 p2
e9 :: Point Float
e9 = midpoint p1 p2

The only way to take apart a point is to pattern match. That is, the two values which constitute a point must be extracted by matching a pattern containing the Pt data constructor. Much more will be said about pattern matching later.

拆解一个点的唯一方法就是模式匹配。也就是说,构成一个点的两个值必须

通过匹配包含 Pt 数据构造器的模式来提取。关于模式匹配,后面还有很多

要讲的内容。

Haskell prints values in the same syntax used in expressions. Thus Pt 1 2 would print as Pt 1 2 (of course, Pt 1 (1+1) would also print as Pt 1 2).

Haskell 用与表达式相同的语法打印值。因此 Pt 1 2 会打印为 Pt 1 2

(当然,Pt 1 (1+1) 也会打印为 Pt 1 2)。