Reputation: 65
I'm implementing Lists in Haskell by myself, but I had a little problem while comparing two Lists for equality,
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
For example, if I have
l1 = (Cons 1 (Cons 2 (Cons 3 (Cons 4 (Cons 5 Void)))))
And if I do l1 == l1
, then instead of printing True
, it prints False
.
What am I missing?
Upvotes: 3
Views: 368
Reputation: 71099
Your equalHelp
implements isSubset subset ofSet
predicate -- almost, except for the fact that it always slides into the empty second list case and then fails.
Instead, stop your recursion on the singleton case:
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
Your second clause assumes a
is a non-empty list, because of the preceding clause. But it is better when each clause's assumptions are clear in the open and the patterns are mutually exclusive (if making it so doesn't make our code overly verbose and thus less readable). Thus, we write it as
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
There's one error left intentionally here, a vestige of your code. One of the clauses is erroneous: an empty set is a subset of any set. Thus the two clauses dealing with an empty set as the first argument can be joined together into one clause.
We've also used the code simplifications
if A then True else False === A
if A then B else False === A && B
in this re-write. There's also
if A then True else C === A || C
which you can use to simplify your myElem
definition.
Another way to rectify your code is to treat it as if defining equality of lists up to ordering of the elements. Removing the head element of the second argument as well on each recursive call as suggested in the comments won't do the trick as we need to remove the found element which might not be -- and likely is not -- the head element. This isn't likely to be very simple, nor efficient, especially if you would also opt to allow for multiplicities of elements. In that case, it is easiest to just define
equalUpToOrderingWithMults a b =
isSubset a b && isSubset b a
The most straightforward way is to just ditch the myElem
call and instead compare only the head elements of the two argument lists at each step of the recursion for the "normal", so to speak, concept of equality.
Upvotes: 4