2

I'm wondering why the email-validate package derives Eq in the following code:

data EmailAddress = EmailAddress ByteString ByteString
   deriving (Eq, Ord, Data, Typeable, Generic) 

I mean, I was using Text for email addresses until I realised I need to make them case-insensitive (so i don't save Example@ex.ample and example@ex.ample as 2 different addresses), so i got to this library only to discover it derives Eq.

So, is it reasonable to derive Eq instead of a self-made case-insensitive instance?. Also, if I were to use this library, how could I provide my own Eq instance for EmailAdress?

Cactus
  • 27,075
  • 9
  • 69
  • 149
RamiroPastor
  • 1,085
  • 1
  • 7
  • 18
  • 3
    You might consider using a smart constructor that normalizes case rather than using the raw `EmailAddress` constructor. – chepner Nov 16 '16 at 22:01
  • 3
    @chepner ...or a bidirectional pattern synonym. Then can even pattern match. – Alec Nov 16 '16 at 22:43

1 Answers1

13

So, is it reasonable to derive Eq instead of a self-made case-insensitive instance?

That sort of depends on what you want. I'm sure that the author of the package has their reasons for that Eq instance.

Also, if i were to use this library, how could i provide my own Eq instance for EmailAdress?

You can't "override" their instance. The usual solution to this sort of problem is a newtype wrapper on which you write your own instances:

newtype MyEmailAddress = MyEmailAddress EmailAddress

And then you are free to define your own version of equality, possibly as:

import Data.Char (toLower)
import qualified Data.ByteString.Char8 as DBC (map)

instance Eq MyEmailAddress where
  MyEmailAddress (EmailAddress a1 d1) == MyEmailAddress (EmailAddress a2 d2) 
    = DBC.map toLower a1 == DBC.map toLower a2 && DBC.map toLower d1 == DBC.map toLower d2

While I'm at it, allow me to mention you can even define a pattern synonym which makes everything much nicer:

{-# LANGUAGE PatternSynonyms #-}
pattern Email address domain = MyEmailAddress (EmailAddress address domain)

Then, you can easily make one of your emails with Email "yourName" "yourDomain" as well as pattern match on that. The Eq instance looks a lot nicer with this:

instance Eq MyEmailAddress where
  Email a1 d1 == Email a2 d2 
    = DBC.map toLower a1 == DBC.map toLower a2 && DBC.map toLower d1 == DBC.map toLower d2
Alec
  • 31,829
  • 7
  • 67
  • 114
  • 1
    I'd add that you can use `GeneralizedNewtypeDeriving` to keep any instances from `EmailAddress` you want. – Alexey Romanov Nov 17 '16 at 07:19
  • @AlexeyRomanov I actually heavily debated whether I wanted to add something about that too. I think I won't because the OP has provided the full data definition of `EmailAddress` and there isn't anything from there you would want to lift using GND. – Alec Nov 17 '16 at 07:41