-1

I am trying to define Eq operator for alternative version numbering approach.

type VersionCompound = Maybe Int -- x, 0, 1, 2, ...

type VersionNumber = [VersionCompound] -- x.x, x.0, x.1, x.2, ... , 1.0, 1.1, 1.2, ... 1.x.x, 2.x.x, 3.x.x, ...

instance Eq VersionNumber where
        [] == [] = True
        (x:[]) == (y:[]) = x == y
        (Nothing:xs) == ys = (xs == ys)
        xs == (Nothing:ys) = (xs == ys)

It is expected that it returns True for following cases: x.x.x == x, 1.x.x == x.1.x.x, x.1 == 1, etc. But instead it returns an error:

VersionNumber.hs:58:34:
    Overlapping instances for Eq [VersionCompound]
      arising from a use of ‘==’
    Matching instances:
      instance Eq a => Eq [a] -- Defined in ‘GHC.Classes’
      instance Eq VersionNumber -- Defined at VersionNumber.hs:55:10
    In the expression: (xs == ys)
    In an equation for ‘==’: (Nothing : xs) == ys = (xs == ys)
    In the instance declaration for ‘Eq VersionNumber’

Any ideas how to fix it?

EDIT: My approach to this problem via pattern matching on lists turned out to be incomplete. I wanted to disregard any arbitrary list of x's (or Nothings) on the left hand side of the given version. So, for example, x.x.x.x.x would be equal to x.x.x and to x. Similarly, x.x.x.1 would be equal to x.x.1 and to 1. If there's an x in the middle, it won't be thrown away. So, for this case, x.x.1.x.0 would be equal to x.1.x.0 and 1.x.0. Yet another example: x.1.x.x.0.x is equal to 1.x.x.0.x and x.1.x.0.x is equal to 1.x.0.x (You just remove x's on the left side).

What I was struggling with after fixing an error Overlapping instances for Eq [VersionCompound] is how to get x.x.x == x -> True with pattern matching. But, as @WillemVanOnsem brilliantly noted, it should be achieved not via pattern matching, but with function composition.

PS. I personally encourage you to upvote the answer by @WillemVanOnsem because his solution is really elegant, required some effort to come up with and represents the essence of Haskell power.

altern
  • 5,829
  • 5
  • 44
  • 72
  • 3
    But `VersionNumber` is a *type alias*... – Willem Van Onsem Aug 07 '17 at 20:17
  • If I do `data VersionNumber = [VersionCompound] deriving (Show, Eq)`, it causes another error: `Cannot parse data constructor in a data/newtype declaration: [VersionCompound]` – altern Aug 07 '17 at 20:27
  • 3
    you should use `data VersionNumber = VersionNumber [VersionCompound]` with a constructor. – Willem Van Onsem Aug 07 '17 at 20:27
  • 1
    @altern For basic syntax errors, see the syntax section of the [Haskell report](https://www.haskell.org/onlinereport/haskell2010/haskellch10.html#x17-18000010.5). – user2407038 Aug 07 '17 at 20:29

1 Answers1

5

You use type aliases. This means that you have not defined a separate type VersionNumber or VersionCompound; you simply have constructed an alias. Behind the curtains, Haskell sees VersionNumber as simply [Maybe Int].

Now if we look at Haskell's library, we see that:

instance Eq Int where
    -- ...

instance Eq a => Eq (Maybe a) where
    -- ...

instance Eq a => Eq [a] where
    -- ...

So that means that Eq Int is defined, that Eq (Maybe Int) is defined as well, and thus that Eq [Maybe Int] is defined by the Haskell library as well. So you actually have already constructed an Eq VersionNumber without writing one. Now you try to write an additional one and, of course, the compiler gets confused with which one to pick.

There are ways to resolve overlapping instances, but this will probably only generate more trouble.

Thus, you better construct a data type with a single constructor. For example:

type VersionCompound = Maybe Int
data VersionNumber = VersionNumber [VersionCompound]

Now, since there is only one constructor, you better use a newtype:

type VersionCompound = Maybe Int
newtype VersionNumber = VersionNumber [VersionCompound]

and now we can define our special instance like:

instance Eq VersionNumber where
    (VersionNumber a) == (VersionNumber b) = a =*= b
        where [] =*= [] = True
              (x:[]) =*= (y:[]) = x == y
              (Nothing:xs) =*= ys = (xs =*= ys)
              xs =*= (Nothing:ys) = (xs =*= ys)

So we thus unwrap the constructors and then use another locally defined function =*= that works like you defined it in your question.

Mind however that you forgot a few patterns in your program, like for instance Just x : xs on both the left and the right side. So, you better fix these first. If I run your code through a compiler, I get the following warnings:

Pattern match(es) are non-exhaustive
In an equation for ‘=*=’:
    Patterns not matched:
        [] (Just _:_)
        [Just _] []
        [Just _] (Just _:_:_)
        (Just _:_:_) []

How you want to handle these cases is of course up to you. @DanielWagner suggests to use:

import Data.Function(on)
import Data.Maybe(catMaybes)

instance Eq VersionNumber where
    (VersionNumber a) == (VersionNumber b) = on (==) catMaybes a b

This will filter out the Nothing values of both VersionNumbers and then check whether the values in the Just generate the same list. So 3.x.2.x.x.1 will be equal to 3.2.1 and x.x.x.x.x.3.2.1.

EDIT: based on your specification in comment, you are probably looking for:

import Data.Function(on)

instance Eq VersionNumber where
    (VersionNumber a) == (VersionNumber b) = on (==) (dropWhile (Nothing ==)) a b
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • 1
    Perhaps ```(=*=) = (==) `on` catMaybes```. – Daniel Wagner Aug 07 '17 at 21:03
  • @Willem Van Onsem: I appreciate the last edit about the `catMaybes`, but this is not quite what I want to achieve. I want to disregard any arbitrary list of `x`s (or `Nothing`s) on the left hand side of the given version. So, for example, `x.x.x.x.x` will be equal to `x.x.x` and to `x`. Likewise, `x.x.x.1` would be equal to `x.x.1` and to `1`. If there's an `x` in the middle, it won't be thrown away. So, for this case, `x.x.1.x.0` would be equal to `x.1.x.0` and `1.x.0`. What I am still struggling with after reading your answer is how to get `x.x.x =*= x` -> `True` with pattern matching. – altern Aug 08 '17 at 11:21
  • @altern: and is `x.1.x.x.0.x` equal to `x.1.x.0.x`? Currently I still have some trouble understanding the specs when two versions are equal. – Willem Van Onsem Aug 08 '17 at 12:26
  • `x.1.x.x.0.x` is equal to `1.x.x.0.x` and `x.1.x.0.x` is equal to `1.x.0.x`. You just remove `x`'s on the left side. – altern Aug 08 '17 at 12:32
  • 1
    @altern: I proposed an implementation that follows your specifications (I think). – Willem Van Onsem Aug 08 '17 at 13:21
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/151427/discussion-between-altern-and-willem-van-onsem). – altern Aug 08 '17 at 17:55