More on type classes and instances
- 本 Handout 备份于此处。(不保证与源文件同步)
在本节中,我们详细研究两个重要的类型类。
- 类型类
Ord
用于有序类型。 - 用于数字类型的
Num
类型。
我们还研究了一个带有约束的实例声明的例子:instance Ord a => Ord [a]
。
这个实例告诉我们,只要 a
是 Ord
的一个实例,那么 [a]
也是。
The type Ordering
and the typeclass Ord
There is also a video on this section.
类型类 Ord
实现了这样的想法:一个类型的元素不仅可以被平等地比较,而且还可以被小于/大于比较。
对一个类型 a
的比较是一个映射 compare : a -> a -> Ordering
,其中 Ordering
类型定义如下。
data Ordering = LT | EQ | GT
deriving (Eq, Ord, Enum, Read, Show, Bounded)
Here, LT
stands for less than, EQ
stands for equal, and GT
stands for greater than.
下面为属于 Eq
类的类型 a
定义了 Ord
类。
class (Eq a) => Ord a where
compare :: a -> a -> Ordering
(<), (<=), (>=), (>) :: a -> a -> Bool
max, min :: a -> a -> a
-- Minimal complete definition:
-- (<=) or compare
-- Using compare can be more efficient for complex types.
compare x y
| x == y = EQ
| x <= y = LT
| otherwise = GT
x <= y = compare x y /= GT
x < y = compare x y == LT
x >= y = compare x y /= LT
x > y = compare x y == GT
-- note that (min x y, max x y) = (x,y) or (y,x)
max x y
| x <= y = y
| otherwise = x
min x y
| x <= y = x
| otherwise = y
在 Ordering
和 Ord
的定义之间似乎存在着一种循环性,因为每个人都提到了另一个。我们可以把这看作是一个相互递归的定义。
例如,在 a
上面的列表上的 compare
函数(其中 a
已经配备了一个 compare
函数)实现如下。
instance (Ord a) => Ord [a] where
compare [] [] = EQ
compare [] (_:_) = LT
compare (_:_) [] = GT
compare (x:xs) (y:ys) = case compare x y of
EQ -> compare xs ys
other -> other
练习
- 通过阅读代码,解释如何比较两个列表。
- 运行一些列表间比较的例子来证实或反驳你的解释。
This video gives an explanation of the implementation of compare :: [a] -> [a] -> Ordering
.
The type class Num
请考虑以下例子
Prelude> 5 + 3
8
Prelude> 3.14159 + 2.71828
5.85987
这里,运算符 +
作用于任何属于 Num
typeclass 实例的类型:对于任何这样的类型 a
,函数 (+)
的类型是 a -> a -> a
,作为一个 infix 运算符使用。
我们可以使用 :info
命令来了解 Num
typeclass 提供了哪些操作:
Prelude> :info Num
class Num a where
(+) :: a -> a -> a
(-) :: a -> a -> a
(*) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
{-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
-- Defined in 'GHC.Num'
instance Num Word -- Defined in 'GHC.Num'
instance Num Integer -- Defined in 'GHC.Num'
instance Num Int -- Defined in 'GHC.Num'
instance Num Float -- Defined in 'GHC.Float'
instance Num Double -- Defined in 'GHC.Float'
这表明任何作为 Num
实例的类型 a
都带有 (+)
, (-)
, (*)
, ..., fromInteger
的操作。(特别是,没有类型注释的 1
不是一个整数,而是任何类型 a
的元素,是 Num
的实例;特别是整数 1 : : Integer
在函数 fromInteger : : Integer -> a
下的图像)。
我们还看到,有五个 instances 定义了 Num
类型,分别是 Word
, Integer
, Int
, Float
和 Double
。当我们想把 1
看作是一个特定的数字类型的元素时,我们可以通过 annotating 它的类型来做到这一点,例如。
Prelude> :type 1 :: Word
1 :: Word :: Word
在这里,我们检查 1 :: Word
,而不是要求 ghci
为我们推断 1
的类型。然后 ghci
只需要检查 Word
是 Num
的一个实例。同样地,对于 (+)
我们可以检查
Prelude> :type (+) :: Integer -> Integer -> Integer
(+) :: Integer -> Integer -> Integer
:: Integer -> Integer -> Integer
对于没有声明 Num
实例的类型,例如 Char
,该检查失败:
Prelude> :type (+) :: Char -> Char -> Char
<interactive>:1:1: error:
• No instance for (Num Char) arising from a use of '+'
• In the expression: (+) :: Char -> Char -> Char
测试时间
通过参加这个测验来测试你的理解。
See also
- A blog post comparing Java and Haskell: https://mmhaskell.com/blog/2019/1/28/why-haskell-iv-typeclasses-vs-inheritance. Do you agree with it? Why (not)?
总结
- 我们详细研究了一个同时使用列表上的模式匹配和
case
表达式(模式匹配的一种更通用的形式)的函数。 - 我们已经看到实例是如何从其他实例中自动派生出来的,使用的例子是
instance Ord a => Ord [a]
。 - 我们看了一下用于数字类型的类型类
Num
。 - 我们看到了如何使用类型注解来强制 Haskell 表达式具有特定的类型,例如,
1 :: Word
。