Skip to main content

Type classes and instances

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

简介

Video: This introduction is also available as a recording.

我们经常想检查 Haskell 中的两个值是否相等。

观察:我们只想比较两个相同类型的值是否相等。询问诸如布尔值是否等于一个字符是没有任何意义的。

因此,我们会想写一个多态的函数

(==) : a -> a -> Bool

然而,并不总是能够决定一个特定类型的两个值是否相等。

练习:找到一个类型 a,其值不能用布尔函数 a -> a -> Bool 进行平等比较。

(视频中给出了一个这样的类型)

解决办法是由 type classes 给出的。

  1. 一个 type class 是对一个(或多个)类型的一组操作的接口。
  2. 一个类型类的 instance 是我们为其实现了接口的任何类型。

在 Haskell 中,操作 (==) 有如下类型。

Prelude> :type (==)
(==) :: Eq a => a -> a -> Bool

在这里,

  1. Eq is a type class, and
  2. 对于任何类型的 a 是类型 Eqinstance(==) 是一个 a -> a -> Bool 类型的函数 - 但只适用于这种 a
  3. Eq a in Eq a => a -> a -> Bool is a class constraint:

练习

  1. 运行并理解以下例子:
    1. False == 'c'
    2. False == True
    3. False == not
    4. False == not True
    5. not == id
    6. [not] == [ (id :: Bool -> Bool) ]

解释: See the video.

对于例子 6,Haskell 知道它应该寻找一个 Eq [Bool -> Bool] 的实例。由于有一个通用的实例 Eq a => Eq [a],Haskell 继续寻找 Eq (Bool -> Bool) 的实例。唉,没有这样的实例,正如我们从例子 5 中知道的那样。

  1. 用你自己的话解释为什么 (++) 不需要任何类约束,而 (==) 需要。

The type class Eq

Video: See here.

我们获得关于类型类Eq的信息如下(为了清晰起见,删除了一些文字)。

Prelude> :info Eq
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
{-# MINIMAL (==) | (/=) #-}
-- Defined in 'GHC.Classes'
instance (Eq a, Eq b) => Eq (Either a b)
-- Defined in 'Data.Either'
instance Eq a => Eq [a] -- Defined in 'GHC.Classes'
instance Eq Word -- Defined in 'GHC.Classes'
instance Eq Ordering -- Defined in 'GHC.Classes'
instance Eq Int -- Defined in 'GHC.Classes'
instance Eq Float -- Defined in 'GHC.Classes'
instance Eq Double -- Defined in 'GHC.Classes'
instance Eq Char -- Defined in 'GHC.Classes'
instance Eq Bool -- Defined in 'GHC.Classes'
...
instance (Eq a, Eq b, Eq c) => Eq (a, b, c)
-- Defined in 'GHC.Classes'
instance (Eq a, Eq b) => Eq (a, b) -- Defined in 'GHC.Classes'
instance Eq () -- Defined in 'GHC.Classes'
instance Eq Integer
-- Defined in 'integer-gmp-1.0.2.0:GHC.Integer.Type'
instance Eq a => Eq (Maybe a) -- Defined in 'GHC.Base'

这告诉我们:

  1. 类型类 Eq 提供两个函数,(==)(\=)
  2. 要在一个类型 a 上实现类型类,我们必须至少实现 (==) :: a -> a -> Bool(\=) :: a -> a -> Bool 中的一个。
  3. 实现了许多 Eq 的实例,例如,对于 WordOrderingInt,...。此外,我们还有衍生的实例:
    1. 如果类型 ab 是实例,那么 ab 中的值对的类型 (a, b) 也是一个实例。
    2. 同样,如果 aEq 的一个实例,那么 a 中数值列表的 [a] 类型也是如此。

上面没有印刷的进一步信息:

  1. 当用户在实现实例时只提供了 (==)(==) 中的一个,另一个就被自动定义为它的否定,例如,x\=y = not (x == y)
  2. 信息中没有说任何实例的 (==) 的实现是什么。这需要查看源代码。

总结: type classes and instances

  • Haskell 中的 class 就像 Java 中的接口。

  • 我们在 Haskell 中使用关键字 instance 实现一个类。

  • 只有使用 datanewtype 引入的类型才能成为类的实例(尽管 GHC 有一些扩展来解决这个问题...)。

  • 对于任何给定的 数据 类型或 新类型,只能声明一个的类的实例(尽管 GHC 有一些扩展来解决这个问题...)。

  • 在一个函数类型中,=> 之前的所有内容都是一个类约束:该函数只适用于作为所述类的实例的这类类型。

继承性: Extending a type class

就像一个 Java 接口可以扩展一个接口一样,一个类型类可以扩展一个类型类。请看下面的例子:

Prelude> :i Ord
class Eq a => Ord a where
...

在这里,类型类 Ord(我们将在下面详细研究)扩展了类型类 Eq。换句话说,为了把一个类型 a 变成 Ord 的一个实例,我们首先需要把它变成 Eq 的一个实例。这一点将在后面详细研究。

测验时间

通过参加这个测验来测试你的理解。

练习

  1. 找到所有在 GHC Prelude 中定义的 Bounded 类型的基本实例(启动 ghci 时加载的库,没有导入任何额外的库)。找出每个实例的 minBoundmaxBound 是什么。
  2. Fractional, Floating, Integral 类型类扩展了哪些类型?它们提供什么功能?你会选择哪个类型类来实现三角函数?
  3. 另一个 type class:
    1. 哪个类型类定义了函数 enumFromTo
    2. 在该类型类的每个实例的元素上评估 enumFromTo
    3. 解释一下 :type enumFromTo 4 8:type enumFromTo 4 (8 :: Int) 之间的不同输出。
  4. 为什么 Haskell 只允许在一个给定类型上的任何类型类的实例?