Skip to main content

More on type classes and instances

提示
  • 本 Handout 备份于此处。(不保证与源文件同步)

在本节中,我们详细研究两个重要的类型类。

  1. 类型类 Ord 用于有序类型。
  2. 用于数字类型的 Num 类型。

我们还研究了一个带有约束的实例声明的例子:instance Ord a => Ord [a]。 这个实例告诉我们,只要 aOrd 的一个实例,那么 [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

OrderingOrd 的定义之间似乎存在着一种循环性,因为每个人都提到了另一个。我们可以把这看作是一个相互递归的定义。

例如,在 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

练习

  1. 通过阅读代码,解释如何比较两个列表。
  2. 运行一些列表间比较的例子来证实或反驳你的解释。

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, FloatDouble。当我们想把 1 看作是一个特定的数字类型的元素时,我们可以通过 annotating 它的类型来做到这一点,例如。

Prelude> :type 1 :: Word
1 :: Word :: Word

在这里,我们检查 1 :: Word,而不是要求 ghci 为我们推断 1 的类型。然后 ghci 只需要检查 WordNum 的一个实例。同样地,对于 (+) 我们可以检查

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

  1. 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)?

总结

  1. 我们详细研究了一个同时使用列表上的模式匹配和 case 表达式(模式匹配的一种更通用的形式)的函数。
  2. 我们已经看到实例是如何从其他实例中自动派生出来的,使用的例子是 instance Ord a => Ord [a]
  3. 我们看了一下用于数字类型的类型类 Num
  4. 我们看到了如何使用类型注解来强制 Haskell 表达式具有特定的类型,例如,1 :: Word