Haskell/高级类型类
类型类可能看起来无害,但对该主题的研究已经导致了一些进步和概括,这些进步和概括使它们成为一个非常强大的工具。
多参数类型类是对单参数类型类的泛化,并由一些 Haskell 实现支持。
假设我们想创建一个“集合”类型类,它可以与各种具体的數據類型一起使用,并支持两个操作——“插入”用于添加元素,以及“成员”用于测试成员资格。第一次尝试可能看起来像这样
示例:Collection
类型类(错误)
class Collection c where insert :: c -> e -> c member :: c -> e -> Bool -- Make lists an instance of Collection: instance Collection [a] where -- insert :: [a] -> e -> [a] insert xs x = x:xs -- member :: [a] -> e -> Bool member = flip elem
然而,这将无法编译,因为 (:)
的参数必须是 [a]
和 a
类型,而我们提供的是 e
。问题是 Collection
操作中的 'e'
类型变量来自无处——Collection
实例的类型中没有任何内容可以告诉我们 'e'
到底是什么,因此我们永远无法定义这些方法的实现。多参数类型类通过允许我们将 'e'
放入类的类型来解决这个问题。这是一个可以编译并使用的示例
示例:Collection
类型类(正确)
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} class Eq e => Collection c e where insert :: c -> e -> c member :: c -> e -> Bool instance Eq a => Collection [a] a where insert = flip (:) member = flip elem
上面示例中的一个问题是,在这种情况下,我们拥有编译器不知道的额外信息,这会导致错误的歧义和过于泛化的函数签名。在这种情况下,我们可以直观地看到集合的类型将始终决定它包含的元素的类型——所以如果 c
是 [a]
,那么 e
将是 a
。如果 c
是 Hashmap a
,那么 e
将是 a
。(反之则不然:许多不同的集合类型可以保存相同的元素类型,因此知道元素类型是例如 Int
,不会告诉你集合类型)。
为了告诉编译器这些信息,我们添加一个函数依赖,将类声明更改为
示例:函数依赖
{-# LANGUAGE FunctionalDependencies #-} class Eq e => Collection c e | c -> e where ...
函数依赖是我们可以对类型类参数施加的约束。这里,额外的 | c -> e
应该读作“c
唯一标识 e
”,意味着对于给定的 c
,将只有一个 e
。你可以在一个类中拥有多个函数依赖——例如,在上述情况下,你可以拥有 c -> e, e -> c
。并且你可以在多参数类中拥有两个以上的参数。
假设你想实现一些代码来执行简单的线性代数
示例:Vector
和 Matrix
数据类型
data Vector = Vector Int Int deriving (Eq, Show) data Matrix = Matrix Vector Vector deriving (Eq, Show)
你希望它们的行为尽可能像数字。因此,你可能会从重载 Haskell 的 Num 类开始
示例:Vector
和 Matrix
的实例声明
instance Num Vector where Vector a1 b1 + Vector a2 b2 = Vector (a1+a2) (b1+b2) Vector a1 b1 - Vector a2 b2 = Vector (a1-a2) (b1-b2) {- ... and so on ... -} instance Num Matrix where Matrix a1 b1 + Matrix a2 b2 = Matrix (a1+a2) (b1+b2) Matrix a1 b1 - Matrix a2 b2 = Matrix (a1-a2) (b1-b2) {- ... and so on ... -}
问题出现在你想要开始将数量相乘时。你真的需要一个重载到不同类型的乘法函数
示例:我们需要什么
(*) :: Matrix -> Matrix -> Matrix (*) :: Matrix -> Vector -> Vector (*) :: Matrix -> Int -> Matrix (*) :: Int -> Matrix -> Matrix {- ... and so on ... -}
我们如何指定一个类型类来允许所有这些可能性?
我们可以尝试这个
示例:无效的尝试(过于泛化)
class Mult a b c where (*) :: a -> b -> c instance Mult Matrix Matrix Matrix where {- ... -} instance Mult Matrix Vector Vector where {- ... -}
然而,那并不是我们真正想要的。就目前而言,即使是像这样的简单表达式也具有模糊的类型,除非你在中间表达式上提供额外的类型声明
示例:歧义导致代码更冗长
m1, m2, m3 :: Matrix (m1 * m2) * m3 -- type error; type of (m1*m2) is ambiguous (m1 * m2) :: Matrix * m3 -- this is ok
毕竟,没有任何东西能阻止别人后来添加另一个实例
示例:Mult
的无意义实例
instance Mult Matrix Matrix (Maybe Char) where {- whatever -}
问题是 c
不应该是一个自由类型变量。当你了解你正在相乘的事物的类型时,结果类型应该仅从此信息中确定。
你可以通过指定函数依赖来表达这一点
示例:Mult
的正确定义
class Mult a b c | a b -> c where (*) :: a -> b -> c
这告诉 Haskell c
由 a
和 b
唯一确定。
至少本页的部分内容是从 Haskell wiki 文章 函数依赖 导入的,根据其简单许可证。如果你希望修改此页面,并且你的更改对该 wiki 也有用,你可以考虑修改该源页面而不是此页面,因为来自该页面的更改可能会传播到这里,反之则不然。或者,你可以明确地将你的贡献双重授权在简单许可证下。 |