【问题标题】:Defining multiple-type container classes in haskell, trouble binding variables在haskell中定义多类型容器类,绑定变量麻烦
【发布时间】:2009-07-28 00:26:52
【问题描述】:

我在使用 haskell 的类时遇到问题。

基本上,我有一个算法(一种奇怪的图形遍历算法),它以一个容器作为输入,其中包括存储已经看到的节点(我热衷于避免单子,所以让我们继续. :))。问题是,该函数将容器作为参数,并且只调用一个函数:“set_contains”,它询问容器...是否包含节点 v。(如果您好奇,作为参数传入的另一个函数会实际的节点添加)。

基本上,我想尝试各种数据结构作为参数。然而,由于没有重载,我不能让多个数据结构与最重要的 contains 函数一起使用!

所以,我想做一个“Set”类(我不应该自己动手,我知道)。多亏了 Chris Okasaki 的书,我已经建立了一个非常漂亮的红黑树,现在剩下的只是创建 Set 类并将 RBT 等声明为它的实例

下面是代码:

(注意:代码大量更新——例如,现在 contains 不调用辅助函数,而是类函数本身!)

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

而且我这辈子都无法弄清楚为什么这不起作用。 (附带说明一下,“contains”函数在别处定义,基本上具有 RBT 数据类型的实际 set_contains 逻辑。)

谢谢! - 阿戈尔

第三次修改:删除之前的修改,合并在上面。

【问题讨论】:

  • @Agor:容器在什么意义上是“多类型容器”?
  • @yairchu:我想这不是正确的标签,但我想不出更好的标签。我试图表达这样一个事实,当我试图制作一个类型为 t 的集合时,它包含一个不同的类型 a,这会导致问题。或者至少,那,但这里的答案非常有帮助。 :) 无论如何,我同意我的标题有点笨拙,如果有更好的写法......

标签: class haskell functional-programming


【解决方案1】:

您也可以使用higher-kinded polyphormism。你的类的定义方式有点期望类型 t 具有kind *。您可能想要的是您的 Set 类采用容器类型,例如您的 RBT 类型 * -> *。

您可以通过将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

如果你编译这个,输出是

树包含 3:真 树包含 12:真 树包含 7:错误

【讨论】:

  • 谢谢!我将此标记为答案,因为它不需要任何语言扩展。我不反对语言扩展,但正如这个答案所表明的那样,我还不知道核心语言(非常好)!首先,我想。 (另外,我可以再次为工作代码示例 +1,谢谢!)
【解决方案2】:

你需要一个多参数类型类。您当前对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 -&gt; a 位是一个函数依赖,表示对于任何给定的t,只有一个可能的a。它很有用(在有意义的情况下),因为它可以帮助编译器找出类型并减少您在使用多参数类型类时经常遇到的模棱两可的类型问题。

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

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

【讨论】:

  • imho TypeFamilies 比FunctionalDependencies 更好。请参阅下面的代码系列
  • 就我个人而言,我认为它们还不够成熟,无法推荐给新手——我自己也广泛使用它们,而且有很多陷阱和一些未实现的部分。一旦他们完成,我同意他们会更好。
  • 虽然我没有将这个标记为答案,但我很高兴被介绍给一些漂亮的新语言扩展(我真的对它们了解不多),我认为 i> 我明白你在说什么。 +1,谢谢。 :)
【解决方案3】:

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

【讨论】:

  • +1 因为我认为它有帮助,但我仍然有同样的类型错误。我将很快更新我所做的代码更改,但到目前为止感谢!
【解决方案4】:

为什么你认为你不应该开设自己的课程?

当您为Set (RBT a) 编写实例时,您仅为特定类型a 定义包含。 IE。 RBT IntInts的集合,RBT BoolBools的集合等

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

也就是说,这应该类型检查,给定contains的类型:

tree :: RBT Bool
tree = ...

foo = contains tree 1

显然不会。

有三种解决方案:

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

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

    instance Set RBT where ...

这适用于RBT,但不适用于许多其他情况(例如,您可能希望将位集用作Ints 的集合。

  1. 函数依赖:

    类 (Ord a) => 设置 t a | t -> 哪里 包含 :: t -> a -> Bool

    instance (Ord a) => 设置 (RBT a) a where ...

详情请见GHC User's Guide

  1. 相关类型:

    class Set t where 类型元素 t :: * 包含 :: t -> 元素 t -> Bool

    实例 (Ord a) => 设置 (RBT a) 其中 类型元素 (RBT a) = a ...

详情请见GHC User's Guide

【讨论】:

    【解决方案5】:

    要扩展 Ganesh 的答案,您可以使用类型族而不是功能依赖项。恕我直言,他们更好。而且他们也更少改变你的代码。

    {-# 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
    

    【讨论】:

    • 哦,这些也很漂亮。谢谢!但正如我在回复 the_edge 时所说,我认为我会推迟使用复杂的语言扩展,直到我有更好的基础。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 2013-05-24
    • 1970-01-01
    相关资源
    最近更新 更多