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 也有用,你可以考虑修改该源页面而不是此页面,因为来自该页面的更改可能会传播到这里,反之则不然。或者,你可以明确地将你的贡献双重授权在简单许可证下。 |