Haskell 中 Eq 比较列表的问题

问题描述

我自己在 Haskell 中实现了列表,但是在比较两个列表是否相等时遇到了一个小问题,

data List a = Void | Cons a (List a) -- deriving (Show,Eq)

instance (Eq a) => Eq (List a) where
  (==) a b = equalHelp a b

equalHelp :: (Eq a) => List a -> List a -> Bool
equalHelp Void Void = True
equalHelp a Void = False
equalHelp Void b = False
equalHelp (Cons a b) l = if (myElem a l) then (equalHelp b l) else False

myElem :: (Eq a) => a -> List a -> Bool
myElem a Void = False
myElem a (Cons c d) = if (a == c) then True
                      else myElem a d

例如,如果我有

l1 = (Cons 1 (Cons 2 (Cons 3 (Cons 4 (Cons 5 Void)))))

如果我执行 l1 == l1,那么它不会打印 True,而是打印 False。 我错过了什么?

解决方法

您的 equalHelp 实现了 isSubset subset ofSet 谓词 -- 几乎,除了它总是滑入空的第二个列表案例然后失败的事实。

相反,在单例情况下停止递归:

isSubset :: (Eq a) => List a -> List a -> Bool
isSubset Void Void = True
isSubset a Void = False
isSubset Void b = False
isSubset (Cons a Void) l = if (myElem a l) then True           else False
isSubset (Cons a b)    l = if (myElem a l) then (isSubset b l) else False

你的第二个子句假设 a 是一个非空列表,因为前面的子句。但是,最好是每个子句的假设都是明确的,并且模式是相互排斥的(如果这样做不会使我们的代码过于冗长,从而降低可读性)。因此,我们把它写成

isSubset :: (Eq a) => List a -> List a -> Bool
isSubset Void         Void  =  True
isSubset (Cons _ _)   Void  =  False
isSubset Void  (Cons _ _)   =  False
isSubset (Cons a Void)  l   =  myElem a l
isSubset (Cons a b)     l   =  myElem a l && isSubset b l

这里故意留下一个错误,你的代码的痕迹。其中一个子句是错误的:空集是 any set 的子集。因此,处理空集作为第一个参数的两个子句可以合并为一个子句。

我们还使用了代码简化

if A then True else False   ===   A
if A then B    else False   ===   A && B

在此重写。还有

if A then True else C       ===   A || C

您可以使用它来简化您的 myElem 定义。


另一种纠正代码的方法是将其视为定义列表的相等性直至元素排序。按照注释中的建议,在每次递归调用时也删除第二个参数的 head 元素不会起作用,因为我们需要删除找到的元素,而这些元素可能不是——也可能不是——head 元素。这可能不是很简单,也不是很有效,特别是如果您还选择允许元素的多样性。在这种情况下,最简单的方法是定义

equalUpToOrderingWithMults a b =
    isSubset a b  &&  isSubset b a

最直接的方法是放弃 myElem 调用,而是在递归的每个步骤中仅比较两个参数列表的头部元素,以获得“正常”,可以说,相等的概念。