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
在这里,
Eq
is a type class, and- 对于任何类型的
a
是类型Eq
的 instance,(==)
是一个a -> a -> Bool
类型的函数 - 但只适用于这种a
。 Eq a
inEq a => a -> a -> Bool
is a class constraint:
练习
- 运行并理解以下例子:
False == 'c'
False == True
False == not
False == not True
not == 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 只允许在一个给定类型上的任何类型类的实例?