在haskell中定义多类型容器类,解决了绑定变量的问题

时间:2009-07-28 00:26:53

标签: class haskell functional-programming

我在haskell上遇到麻烦。

基本上,我有一个算法(一种奇怪的图形遍历算法),除了其他之外,还有一个容器来存储已经看过的节点(我很想避开monad,所以让我们继续前进。:))。问题是,该函数将容器作为参数,并且只调用一个函数:“set_contains”,它询问容器...是否包含节点v。(如果你很好奇,另一个作为参数传入的函数会实际的节点添加)。

基本上,我想尝试各种数据结构作为参数。然而,由于没有重载,我不能使用多个包含函数的多个数据结构!

所以,我想做一个“Set”课(我不应该自己动手,我知道)。由于Chris Okasaki的书,我已经设置了一个漂亮漂亮的红黑树,现在剩下的只是制作Set类并宣布RBT等等作为它的实例。 / p>

以下是代码:

(注意:代码经过大量更新 - 例如,包含现在不调用辅助函数,但是类函数本身!)

data Color = Red | Black
data (Ord a) => RBT a = Leaf | Tree Color (RBT a) a (RBT a)

instance Show Color where
    show Red = "r"
    show Black = "b"

class Set t where
    contains :: (Ord a) => t-> a-> Bool

-- I know this is nonesense, just showing it can compile.
instance (Ord a) => Eq (RBT a) where
    Leaf == Leaf = True
    (Tree _ _ x _) == (Tree _ _ y _) = x == y

instance (Ord a) => Set (RBT a) where
    contains Leaf b = False
    contains t@(Tree c l x r) b
        | b == x    = True
        | b < x     = contains l b
        | otherwise = contains r b

请注意我有一个非常愚蠢定义的RBT Eq实例。这是故意的 - 我从the gentle tutorial复制了它(但是偷工减料)。

基本上,我的问题归结为:如果我注释掉Set(RBT a)的实例化语句,那么一切都会编译。如果我重新添加它,我会收到以下错误:

RBTree.hs:21:15:
    Couldn't match expected type `a' against inferred type `a1'
      `a' is a rigid type variable bound by
          the type signature for `contains' at RBTree.hs:11:21
      `a1' is a rigid type variable bound by
           the instance declaration at RBTree.hs:18:14
    In the second argument of `(==)', namely `x'
    In a pattern guard for
       the definition of `contains':
          b == x
    In the definition of `contains':
        contains (t@(Tree c l x r)) b
                   | b == x = True
                   | b < x = contains l b
                   | otherwise = contains r b

我根本不能,为了我的生活,弄清楚为什么那不起作用。 (作为旁注,“包含”函数在别处定义,并且基本上具有RBT数据类型的实际set_contains逻辑。)

谢谢! - Agor

第三次修改:删除了之前合并的上述修改。

5 个答案:

答案 0 :(得分:4)

您需要一个多参数类型类。您当前对Set t的定义未提及类定义中包含的类型,因此成员contains必须适用于任何a。试试这个:

class Set t a | t -> a where
    contains :: (Ord a) => t-> a-> Bool


instance (Ord a) => Set (RBT a) a where
    contains Leaf b = False
    contains t@(Tree c l x r) b
        | b == x    = True
        | b < x     = contains l b
        | otherwise = contains r b

定义的| t -> a位是功能依赖,表示对于任何给定的t,只有一个可能的a。它有用(当它有意义时),因为它有助于编译器找出类型并减少多参数类型类通常会产生的模糊类型问题的数量。

您还需要在源文件的顶部启用语言扩展程序MultiParamTypeClassesFunctionalDependencies

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

答案 1 :(得分:4)

您也可以使用higher-kinded polyphormism。定义类的方式,它需要一个具有kind *的类型t。你可能想要的是你的Set类采用容器类型,就像你的RBT有类型* - &gt; *。

您可以轻松修改您的课程,为您的类型提供类型* - &gt; *将t应用于类型变量,如下所示:

class Set t where
    contains :: (Ord a) => t a -> a -> Bool

然后修改实例声明以删除类型变量a

instance Set RBT where
    contains Leaf b = False
    contains t@(Tree c l x r) b
        | b == x    = True
        | b < x     = contains l b
        | otherwise = contains r b

所以,这里是完整修改后的代码,最后有一个小例子:

data Color = Red | Black
data (Ord a) => RBT a = Leaf | Tree Color (RBT a) a (RBT a)

instance Show Color where
    show Red = "r"
    show Black = "b"

class Set t where
    contains :: (Ord a) => t a -> a -> Bool

-- I know this is nonesense, just showing it can compile.
instance (Ord a) => Eq (RBT a) where
    Leaf == Leaf = True
    (Tree _ _ x _) == (Tree _ _ y _) = x == y

instance Set RBT where
    contains Leaf b = False
    contains t@(Tree c l x r) b
        | b == x    = True
        | b < x     = contains l b
        | otherwise = contains r b

tree = Tree Black (Tree Red Leaf 3 Leaf) 5 (Tree Red Leaf 8 (Tree Black Leaf 12 Leaf))

main =
    putStrLn ("tree contains 3: " ++ test1) >>
    putStrLn ("tree contains 12: " ++ test2) >>
    putStrLn ("tree contains 7: " ++ test3)
    where test1 = f 3
          test2 = f 12
          test3 = f 7
          f = show . contains tree

如果编译它,则输出为

tree contains 3: True
tree contains 12: True
tree contains 7: False

答案 2 :(得分:1)

错误表示类型不匹配。 contains的类型是什么? (如果它的类型不像t -> a -> Bool那样set_contains,那就错了。)

答案 3 :(得分:1)

为什么你认为你不应该推出自己的课程?

Set (RBT a)编写实例时,只为特定类型a定义包含。即RBT Int是一组IntRBT Bool是一组Bools等。

但是您对Set t的定义要求t同时是一组所有订购的a

也就是说,考虑到contains

的类型,这应该是类型检查
tree :: RBT Bool
tree = ...

foo = contains tree 1

,显然不会。

有三种解决方案:

  1. 使t成为类型构造函数变量:

    class Set t where   contains ::(ord a)=&gt; t a - &gt; A-&GT;布尔

    实例设置RBT在哪里   ......

  2. 这适用于RBT,但不适用于其他许多情况(例如,您可能希望将bitset用作Int的一组。

    1. 功能依赖:

      class(Ord a)=&gt;设置t a | t - &gt;在哪里   contains :: t - &gt; a - &gt;布尔

      instance(Ord a)=&gt;设置(RBT a)a在哪里   ......

    2. 有关详细信息,请参阅GHC User's Guide

      1. 相关类型:

        class Set t where   type element t :: *   contains :: t - &gt;元素t - &gt;布尔

        instance(Ord a)=&gt;设置(RBT a)在哪里   type元素(RBT a)= a   ...

      2. 有关详细信息,请参阅GHC User's Guide

答案 4 :(得分:1)

要扩展Ganesh的答案,您可以使用Type Families而不是Functional Dependencies。 Imho他们更好。而且他们也减少了你的代码。

{-# LANGUAGE FlexibleContexts, TypeFamilies #-}

class Set t where
  type Elem t
  contains :: (Ord (Elem t)) => t -> Elem t -> Bool

instance (Ord a) => Set (RBT a) where
  type Elem (RBT a) = a
  contains Leaf b = False
  contains (Tree c l x r) b
    | b == x    = True
    | b < x     = contains l b
    | otherwise = contains r b