Haskell Bag(Multiset)实现

时间:2012-06-24 06:25:50

标签: haskell syntax-error

我正在尝试实现一个Haskell Bag(multiset)。

到目前为止,我已经有了这个

data Bag a = EmptyBag | ListBag [(a, Integer)] deriving (Eq, Show)

emptyBag :: Bag a
emptyBag = EmptyBag

add :: a -> (Bag a) -> (Bag a)
add element EmptyBag = ListBag [(element,1)]
add element (ListBag bag)
  | element `elem` map fst bag = ListBag bag -- will actually increment the count, and return the new bag.

我收到错误

No instance for (Eq a)
      arising from a use of `elem'
    In the expression: element `elem` map fst bag

编译时。 这是因为你无法确定两种不同类型的平等吗?如何确定行李中物品的第一个元素是否已经放入行李中?

此外,有关如何实现递增特定Item计数,以及使用new(element,count)元组返回包的任何提示?

2 个答案:

答案 0 :(得分:6)

问题的直接原因是并非所有类型都具有可比性。您可以通过更改类型签名将类型限制为仅与提供相等性比较的类型一起使用:

add :: Eq a => a -> Bag a -> Bag a

您可能需要查看Hackage上的multiset-combdata-ordlist包,以获取进一步的实施提示。

作为最后一点,我发现EmptyBag构造函数有点怀疑:它与例如ListBag []有什么不同?

答案 1 :(得分:4)

是的,问题是Haskell无法比较任意元素的相等性 - 它只能比较属于Eq类型类的类型。这是有道理的:比较某些事情,比如功能,是不可判断的。其他语言都有“引用相等”的概念,但这在Haskell中没有意义。所以有些类型的价值从根本上无法进行平等比较。您无法检查列表中是否已存在某些内容,除非您有某种方法可以比较两个值的相等性,这是Eq提供的。

这意味着任何set(或multiset)实现都将依赖于Eq(或其他一些显式比较函数)。实际上,出于性能原因,集合往往也依赖于Ord,但由于您只是使用列表,因此不必担心。这也意味着你不能让你的multiset成为Functor或Monad,但是你可以选择。

简而言之:您必须将类型约束为Eq。因此,请将a -> Bag a -> Bag g更改为Eq a => a -> Bag a -> Bag a,依此类推。

因为看起来你只是做一些练习来学习这门语言(我希望这不会像居高临下一样),我只会给你一些关于第二个问题的提示。

递归思考。首先,考虑一个基本情况:如何将元素添加到空多集?另一个基本案例:给定一个以元素作为列表头部的多集合,如何创建一个新的递增多集?最后,递归情况:如果你有一个列表,其中头不是你要增加的元素,你会怎么做?一旦你回答了所有这些问题,你就可以通过列表上的模式匹配将每个问题写成一个案例,并将它们组合在一起以获得add函数。

另一个注意事项:拥有EmptyBag构造函数是多余的。列表已经是空的! ListBag []EmptyBag的区别如何?在这种情况下我只有一个构造函数。

所以你的添加功能将如下所示:

add :: Eq a => a -> Bag a -> Bag a
add x (ListBag []) = ...
add x (ListBag [(x', n)]) = ...

只需使用适当的案例填写...即可。

根据您的评论,这里有一些示例代码,介绍如何在递归时保留列表。

基本上,主要思想很简单:在递归的情况下,不是只返回传递给函数的列表的其余部分,而是返回当前元素,后跟列表的其余部分。基本情况仍然很简单:

add :: Eq a => a -> Bag a -> Bag a
add x (ListBag []) = ListBag [(x, 1)] -- first base case
add x (ListBag (x', n):xs)
  | x == x'   = ListBag $ (x', n + 1) : xs -- second base case
  | otherwise = let ListBag rest = add x (ListBag xs) in ListBag $ (x', n) : rest

您必须使用let语句从ListBag中获取列表,以便您可以将未触及的元组放在其前面。

在考虑这样的递归时,我宁愿不把它想象成一系列步骤,而是分别考虑每个案例。在每种情况下,我们都希望返回我们获得的整个 ListBag。所以我们需要将我们正在处理的元组与列表的其余部分联系起来。在递归的情况下,我们从递归调用获得列表的其余部分;在第二个基本情况下,我们不必再次调用该函数。

因此,通过在每一步返回整个包,我们在所有递归结束时维护整个列表。

我希望这更清楚。