4

I try to write a function to check whether two vectors are equal or not:

Compare Vec1 [1, 2, 3]   Vec1 [1, 2, 4]
Compare VecS ["a","b", "c"] VecS ["a", "b", "d"]

However, on my function I have "almost" identical implementation for both cases,

is there a way to simplify my code so that it has only one implementation for Integer and String

data MyVector = Vec1 [Integer] | VecS[String]  

eqAssert::MyVector->MyVector->Bool
eqAssert (Vec1 []) (Vec1 []) = True
eqAssert (Vec1  _) (Vec1 []) = False 
eqAssert (Vec1 (x:cx)) (Vec1 (y:cy)) =
  if length (x:cx) /= length (y:cy)
    then False
    else (if x /= y then False else eqAssert (Vec1 cx) (Vec1 cy))

eqAssert (VecS []) (VecS []) = True
eqAssert (VecS  _) (VecS []) = False 
eqAssert (VecS (x:cx)) (VecS (y:cy)) =
  if length (x:cx) /= length (y:cy)
    then False
    else (if x /= y then False else eqAssert (VecS cx) (VecS cy))
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
Aron Lee
  • 607
  • 1
  • 5
  • 8
  • 2
    I think you make the comparisons rather hard to begin with. – Willem Van Onsem Mar 11 '18 at 08:03
  • 1
    The answers below suggest the right solution. However, you should turn on warnings! Above you missed the case `eqAssert (Vec1 []) (Vec1 (x:xs))`, making your program crash when the first list is shorter -- warnings can spot this for you. Further, there is no reason to check for the length, since it is implied by the other checks. Finally, your `if`s are hard to read, and could be replaced by `... && ... && ...`. – chi Mar 11 '18 at 08:45
  • On the warnings chi mentions, see also: [*Better exception for non-exhaustive patterns in case*](https://stackoverflow.com/questions/2737650/better-exception-for-non-exhaustive-patterns-in-case) – duplode Mar 11 '18 at 12:08

2 Answers2

5

I think you aim to do too much work yourself. Haskell has an Eq typeclass, and an equality check (==) :: Eq a => a -> a -> a function for that.

Furthermore in Haskell for every a such that Eq a holds, it also means that Eq [a] holds. In that case the equality check is done on lists such that two lists are equal given these have the same number of elements, and if we enumerate the two lists in parallel, each element of the first list is equal to the corresponding element in the other list.

So wen can simplify the function like:

eqAssert :: MyVector -> MyVector -> Bool
eqAssert (Vec1 a) (Vec1 b) = a == b
eqAssert (VecS a) (VecS b) = a == b
eqAssert _ _ = False

So the function has three clauses. The first one deels with two Vec1s, in that case we check the equality of the two lists a and b. The second clause is almost identical, except that we check for two VecSs. Finally we use eqAssert _ _. Here _ is a wildcard: it matches everything. The remaining patterns are (Vec1 a) (VecS b) and (VecS a) (Vec1 b) regardless what a and b are, we simply return False in that case.

It is furthermore a bit odd to implement an eqAssert here. We can make it automatically an instance of Eq, in that case Haskell will implement an (==) function for MyVector automatically. So we can write:

data MyVector = Vec1 [Integer] | VecS[String] deriving Eq

and then use myvec1 == myvec2 instead.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • N.B. this is exactly the function that `deriving (Eq)` would write for you. – luqui Mar 11 '18 at 10:46
  • @luqui: yes, although that would imply that we make it an instance of `Eq` and furthermore that we thus loose the freedom to implement `(==)` in another way. But you are right. – Willem Van Onsem Mar 11 '18 at 13:23
4

You can take advantage of the fact that both Integer and String are Eq instances, and for all Eq a, [a] is also an instance of Eq.

You you can pattern-match the lists out of their data constructors and compare them:

eqAssert :: MyVector -> MyVector -> Bool
eqAssert (Vec1 xs) (Vec1 ys) = xs == ys
eqAssert (VecS xs) (VecS ys) = xs == ys
eqAssert        _         _  = False

Why not make MyVector an instance of Eq, though?

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736