Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Override default implementations for derived typeclass instances

I have defined a datatype for which I allow GHC to automatically derive an instance of the Eq typeclass.

However, the derived instance doesn't have the precise behavior I need for all cases.

For instance, see this datatype:

data IntegerOrFloat = I Integer | F Float
  deriving Eq

(I 0) == (F 0) evaluates to False. I would like it to evaluate to true. If I were writing the implementation I could simply do:

instance Eq IntegerOrFloat where
  (I i) == (F f) = (fromIntegral i) == f

However, I would then have to write the other three cases. Obviously this would be trivial for this type, but this is a contrived example. I very much prefer the convenience of leaving most cases automatically derived.

Is there a way for me to "override" the derived implementation for a specific case, without having to write the entire implementation by hand?

like image 280
SpencerB Avatar asked Mar 18 '26 11:03

SpencerB


2 Answers

You can make use of the generic-deriving package [Hackage], this makes implementations for types that are a member of the Generic type class:

{-# LANGUAGE DeriveGeneric #-}

import Generics.Deriving.Base(Generic)
import Generics.Deriving.Eq(GEq, geq)

data IntegerOrFloat = I Integer | F Float
  deriving (Generic)

instance GEq IntegerOrFloat

instance Eq IntegerOrFloat where
    F f == I i = fromIntegral i == f
    I i == F f = fromIntegral i == f
    x == y = geq x y

geq :: GEq a => a -> a -> Bool is thus an instance that automatically generate an (==) function like Haskell does this, we can then use geq this as a base function.

like image 160
Willem Van Onsem Avatar answered Mar 21 '26 03:03

Willem Van Onsem


You could leave the derived type as-is, but export only a newtype-wrapped version that has the special case overridden:

data IntegerOrFloat' = I' Integer | F' Float
  deriving Eq

newtype IntegerOrFloat = IOF { getIOF :: IntegerOrFloat' }

instance Eq IntegerOrFloat where
  IOF (I i) == IOF (F f) = fromIntegral i == f
  IOF (F f) == IOF (I i) = f == fromIntegral i
  IOF x == IOF y = x==y

But I really find this rather dubious. If you need different Eq behaviour, then you should probably implement everything completely by hand – different Eq instance means pretty much everything should treat F 0 and I 0 as if they were the same value, which is not what the compiler assumes with derived instances.

like image 35
leftaroundabout Avatar answered Mar 21 '26 03:03

leftaroundabout



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!