具有多个参数的Haskell和类型类

时间:2011-06-03 22:14:05

标签: haskell

在编写从彼此继承的类型类时,我遇到了一个简单的问题。我正在尝试创建一个类型类的层次结构,以实现某种程度的表示抽象。假设我想要一个集合类型类:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}

class Collection c a where
  isMember :: a -> c -> Bool

我已经定义了树类型:

data Tree a = Empty | Node a (Tree a) (Tree a)
  deriving (Show, Eq)

我想让我的树成为一个集合,所以:

inOrder :: Tree a -> [a]
inOrder Empty = []
inOrder (Node a l r) = (inOrder l) ++ [a] ++ (inOrder r)

instance (Eq a) => Collection (Tree a) a where
  isMember a c = a `elem` (inOrder c)

这不太正常:

*Main> (isMember '2' Empty)
<interactive>:1:1:
    No instance for (Collection (Tree a) Char)
      arising from a use of `isMember' at <interactive>:1:1-18
    Possible fix:
      add an instance declaration for (Collection (Tree a) Char)
    In the expression: (isMember '2' Empty)
    In the definition of `it': it = (isMember '2' Empty)

如果必须为每种具体类型创建一个实现,大概类节类的值就会丢失。所以我没有正确编写实例声明。但我无法弄清楚如何继续。

3 个答案:

答案 0 :(得分:11)

这里的问题是,默认情况下,每个类型类参数都独立于其他参数。简单地将isMember应用于Char和未知元素类型的树是不足以让它推断它应该使用(看似明显的)实例。

这显然不是你希望它如何工作,而且引导有点傻,但这就是它的工作原理。要解决这个问题,你需要给它一些建立连接的方法,其中还有其他扩展:FunctionalDependencies更常见,而TypeFamilies更新,更好用,但仍然有一些粗糙的边缘。

对于函数依赖项,您可以指定类型类参数的某些子集完全确定其他子集,例如Collection c a | c -> a。这有其自身的缺陷,但在许多情况下运作良好。

对于类型族,您可能会将其减少为单参数类型类,并且元素类型相关联,例如:

class Collection c where
    type Elem c
    isMember :: Elem c -> c -> Bool

答案 1 :(得分:7)

您需要添加功能依赖项以指示容器类型确定元素类型:

{-# LANGUAGE FunctionalDependencies #-}

class Collection c a | c -> a where
    ...

还有其他方法可以做到这一点,比如使用类型系列,但我认为这是最简单的修复方法。

答案 2 :(得分:3)

问题是,Empty的类型为Tree a,ghc没有足够的信息知道您希望a在这种情况下为Char 。如果手动指定类型,则可以:

isMember '2' (Empty::Tree Char)

ghc不能仅仅推断a需要Char的原因是理论上你可以有一个实例Collection (Tree Char) Char和另一个Collection (Tree Int) Char。在这种情况下,除非你明确指定,否则无法推断应该使用哪一个。