Reputation: 1094
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
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
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
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
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
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