A Sz
A Sz

Reputation: 1094

(Re)-defining (==) for class Eq

In the following example:

data ColourName 
  = White
  | Grey
  | Gray
  | Black
  | Blue
  -- ...
  -- hundreds more of colours
  -- ...
  | LastColor
  deriving (Read, Show, Eq)

I'd like to redefine (==) so that Grey and Gray evaluate as equal.

Obviously, one way would be to not include Eq in deriving, however, then I'd have to define

(==) :: ColourName
(==) White White = True
(==) Gray Gray = True
(==) Grey Grey = True
(==) Gray Grey = True
(==) Grey Gray = True
(==) Black Black = True
-- frickin' log of other colors, hundreds of lines of typing
(==) LastColor LastColor = True
(==) a b = False

which is nothing I plan to do.

I also can't do

instance Eq ColourName where
    (==) :: ColourName -> ColourName -> Bool
    (==) Gray Grey = True
    (==) Grey Gray = True
    (==) a b = (a == b)

because this leads to an infinite recursion, is basically underdefined.

Is there a way out?

(No, I don't want to use data Colour = Colour String or similar. I want the valid colours to be represented as an enumeration, such providing automatic validation, but want to allow spelling variation for the end users of the module!)

Upvotes: 4

Views: 217

Answers (5)

yatima2975
yatima2975

Reputation: 6610

I would use a newtype here:

newtype ColourNameEquatingGrayAndGrey = CNEGAG ColourName
instance Eq ColourNameEquatingGrayAndGrey where
    CNEGAG Gray == CNEGAG Grey = True
    CNEGAG Grey == CNEGAG Gray = True
    CNEGAG a    == CNEGAG b    = a == b

(Sorry about the silly type and constructor names...)

This allows you to keep deriving Eq, it makes you be very explicit about where in your code you are lumping the different spellings together, and you can still use library functions such as nub (as compared to having to switch over to nubBy sameColour (as in @cdk's answer) or something like that). You can also make your own Show instance, should you need one, and the runtime cost should be minimal.

The only downside I can think of right now is that pattern matching becomes more cumbersome, but I'm guessing that with 100s of alternatives that's not something you do at the drop of a hat!

Upvotes: 4

alternative
alternative

Reputation: 13042

Do not do this. It won't work well with pattern matching. It will break something like

f Gray = g
f x    = h

because pattern matching does not care about your Eq instance.

By break, I mean it won't have the behavior you want, since f Grey would end up calling h rather than g, even though you would expect for f x == f y for all x == y. This means the programmer has to explicitly remember to make cases for both f Gray and f Grey which is just dumb.

If you are determined to have an ugly hack to allow for alternate spellings, I suppose you can do

#define Gray Grey

with CPP enabled.

Upvotes: 9

Piezoid
Piezoid

Reputation: 638

You can use the derived Enum instance :

data ColourName = Gray | Grey | ...
  deriving (Read, Show, Enum)

instance Eq ColourName where
  Gray == Grey = True
  Grey == Gray = True
  a == b = fromEnum a == fromEnum b

Edit: You can also use PatternSynonyms with GHC 7.8+. It works like a smart constructor, but can also be used in pattern matches.

pattern Gray = Grey

Upvotes: 12

bheklilr
bheklilr

Reputation: 54078

Similarly to Piezoid's answer, you could make it a bit less efficient by using the Show instance to compare them:

data ColourName = Gray | Grey | ...
    deriving (Show, Read)

instance Eq ColourName where
    Gray == Grey = True
    Grey == Gray = True
    a == b = show a == show b

Then you don't have to rely on using Enum, but you will have a bit of a performance hit from having to compare strings.

Upvotes: 6

cdk
cdk

Reputation: 6778

By definition the values Grey and Gray are not equal. There is nothing that suggests that they should be equal, except the extra semantics you've attached to them. I'd say this is an abuse of the Eq typeclass.

Define a function to handle these additional semantics:

sameColour :: Color -> Color -> Bool
sameColour Grey Gray = True
sameColour Gray Grey = True
sameColor  a    b    = a == b

this can easily be extended to handle multiple colour "synonyms"

Upvotes: 7

Related Questions