Type classes and instances
- 本 Handout 备份于此处。(不保证与源文件同步)
- 关于所有预定义的类和它们的实例,请参见当前版本的语言的前奏。
简介
Video: This introduction is also available as a recording.
我们经常想检查 Haskell 中的两个值是否相等。
观察:我们只想比较两个相同类型的值是否相等。询问诸如布尔值是否等于一个字符是没有任何意义的。
因此,我们会想写一个多态的函数
(==) : a -> a -> Bool
然而,并不总是能够决定一个特定类型的两个值是否相等。
练习:找到一个类型 a,其值不能用布尔函数 a -> a -> Bool 进行平等比较。
(视频中给出了一个这样的类型)
解决办法是由 type classes 给出的。
- 一个 type class 是对一个(或多个)类型的一组操作的接口。
- 一个类型类的 instance 是我们为其实现了接口的任何类型。
在 Haskell 中,操作 (==) 有如下类型。
Prelude> :type (==)
(==) :: Eq a => a -> a -> Bool
在这里,
Eqis a type class, and- 对于任何类型的
a是类型Eq的 instance,(==)是一个a -> a -> Bool类型的函数 - 但只适用于这种a。 Eq ainEq a => a -> a -> Boolis a class constraint:
练习
- 运行并理解以下例子:
False == 'c'False == TrueFalse == notFalse == not Truenot == id[not] == [ (id :: Bool -> Bool) ]
解释: See the video.
对于例子 6,Haskell 知道它应该寻找一个 Eq [Bool -> Bool] 的实例。由于有一个通用的实例 Eq a => Eq [a],Haskell 继续寻找 Eq (Bool -> Bool) 的实例。唉,没有这样的实例,正如我们从例子 5 中知道的那样。
- 用你自己的话解释为什么
(++)不需要任何类约束,而(==)需要。
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'
这告诉我们:
- 类型类
Eq提供两个函数,(==)和(\=)。 - 要在一个类型
a上实现类型类,我们必须至少实现(==) :: a -> a -> Bool或(\=) :: a -> a -> Bool中的一个。 - 实现了许多
Eq的实例,例如,对于Word、Ordering、Int,...。此外,我们还有衍生的实例:- 如果类型
a和b是实例,那么a和b中的值对的类型(a, b)也是一个实例。 - 同样,如果
a是Eq的一个实例,那么a中数值列表的[a]类型也是如此。
- 如果类型
上面没有印刷的进一步信息:
- 当用户在实现实例时只提供了
(==)和(==)中的一个,另一个就被自动定义为它的否定,例如,x\=y = not (x == y)。 - 信息中没有说任何实例的
(==)的实现是什么。这需要查看源代码。
总结: type classes and instances
Haskell 中的
class就像 Java 中的接口。我们在 Haskell 中使用关键字
instance实现一个类。只有使用
data或newtype引入的类型才能成为类的实例(尽管 GHC 有一些扩展来解决这个问题...)。对于任何给定的
数据类型或新类型,只能声明一个的类的实例(尽管 GHC 有一些扩展来解决这个问题...)。在一个函数类型中,
=>之前的所有内容都是一个类约束:该函数只适用于作为所述类的实例的这类类型。
继承性: Extending a type class
就像一个 Java 接口可以扩展一个接口一样,一个类型类可以扩展一个类型类。请看下面的例子:
Prelude> :i Ord
class Eq a => Ord a where
...
在这里,类型类 Ord(我们将在下面详细研究)扩展了类型类 Eq。换句话说,为了把一个类型 a 变成 Ord 的一个实例,我们首先需要把它变成 Eq 的一个实例。这一点将在后面详细研究。
测验时间
通过参加这个测验来测试你的理解。
练习
- 找到所有在 GHC Prelude 中定义的
Bounded类型的基本实例(启动ghci时加载的库,没有导入任何额外的库)。找出每个实例的minBound和maxBound是什么。 Fractional,Floating,Integral类型类扩展了哪些类型?它们提供什么功能?你会选择哪个类型类来实现三角函数?- 另一个 type class:
- 哪个类型类定义了函数
enumFromTo? - 在该类型类的每个实例的元素上评估
enumFromTo。 - 解释一下
:type enumFromTo 4 8和:type enumFromTo 4 (8 :: Int)之间的不同输出。
- 哪个类型类定义了函数
- 为什么 Haskell 只允许在一个给定类型上的任何类型类的实例?