Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create instance class for a specific constructor

Tags:

haskell

Say I have the following data:

data A
  = B Int
  | C Float
  | D A

If I want to derive Eq, but change the output so that comparing two items with constructor D will always equal, is there a way to do so without implementing it for all other constructors? For the other cases, I would like to derive the default Eq implementation.

What I want to achieve is something along the lines of

instance Eq A where
  (D _) == (D _) = True
  _ == _ = undefined -- Use default eq
like image 793
Allan W Avatar asked Sep 14 '26 07:09

Allan W


1 Answers

There's no straightforward way to incorporate the default Eq A instance generated by GHC into your own Eq A instance. The problem is that generation of the code for the instances is tied to the process of defining those instances -- the only way to generate the default Eq A code is to actually generate the unique Eq A instance, and once the instance is generated, you can't really change it. I don't see any way, even with GHC "deriving"-related extensions, of working around this problem.

However, there's a reimplementation of the default Eq instance provided by the package generic-deriving which you could use. With some preamble:

{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
import Generics.Deriving.Eq  -- from package generic-deriving

define your data type with a derived Generic instance:

data A
  = B Int
  | C Float
  | D A
  deriving (Generic)

Then, define a GEq instance that implements your special case for the D constructor while deferring to the default implementation for the rest.

instance GEq A where
  D _ `geq` D _ = True
  x   `geq` y   = x `geqdefault` y

Finally, define an Eq instance the uses this generic equality class.

instance Eq A where
  (==) = geq

After that, it should all work as expected:

> D (B 10) == D (B 20)
True
> B 10 == B 20
False
> 

However, it might be more reasonable to take the advice in the comments, and either:

  1. Do what @malloy suggests. The operation you're trying to define isn't really (==), so why do need to name it (==)? Just derive the usual Eq instance and write a separate function to avoid the unwanted recursion:

    equalClasses :: A -> A -> Bool
    equalClasses (D _) (D _) =  True
    equalClasses x y = x == y
    
  2. If you really want to use a (==), I think using a newtype as suggested by @luqui is probably the most idiomatic approach:

    data A'
      = B Int
      | C Float
      | D A'
      deriving (Eq)
    
    newtype A = A A'
    
    instance Eq A where
      A (D _) == A (D _) = True
      A x == A y = x == y
    
like image 96
K. A. Buhr Avatar answered Sep 17 '26 00:09

K. A. Buhr



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!