3

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
10
  • Kind of. Derive Eq for A, then make a newtype A' = A' A, with a custom Eq. Then A' is your type. Whether that's with you the hassle depends on the situation Commented Mar 29, 2019 at 23:42
  • 4
    This wouldn't be a lawful Eq instance. Eq is supposed to mean "nothing in the object's public API can tell these two objects apart". Consider calling this comparison function something other than (==). Commented Mar 29, 2019 at 23:59
  • @amalloy do you have a suggestion to handle equality for cyclic types? The reason why I wanted to modify Eq is to avoid unending equality checks. Commented Mar 30, 2019 at 0:11
  • I'm sort of curious to know the real world ask that is hidden behind a stack overflow question. Commented Mar 30, 2019 at 0:13
  • 2
    @AllanW the natural derived instance wouldn't give "unending equality checks". It would certainly have recursive equality checks, but provided your actual values don't have infinite recursion then the equality checks won't either. After all Haskell lists are a recursive data type, with an unproblematic Eq instance - and I don't see your example as being fundamentally different. Commented Mar 30, 2019 at 0:51

1 Answer 1

2

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
    
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.