I'd like to be able to derive Eq
and Show
for an ADT that contains multiple fields. One of them is a function field. When doing Show
, I'd like it to display something bogus, like e.g. "<function>"
; when doing Eq
, I'd like it to ignore that field. How can I best do this without hand-writing a full instance for Show
and Eq
?
I don't want to wrap the function field inside a newtype
and write my own Eq
and Show
for that - it would be too bothersome to use like that.
Typically what I do in this circumstance is exactly what you say you don’t want to do, namely, wrap the function in a newtype
and provide a Show
for that:
data T1
{ f :: X -> Y
, xs :: [String]
, ys :: [Bool]
}
data T2
{ f :: OpaqueFunction X Y
, xs :: [String]
, ys :: [Bool]
}
deriving (Show)
newtype OpaqueFunction a b = OpaqueFunction (a -> b)
instance Show (OpaqueFunction a b) where
show = const "<function>"
If you don’t want to do that, you can instead make the function a type parameter, and substitute it out when Show
ing the type:
data T3' a
{ f :: a
, xs :: [String]
, ys :: [Bool]
}
deriving (Functor, Show)
newtype T3 = T3 (T3' (X -> Y))
data Opaque = Opaque
instance Show Opaque where
show = const "..."
instance Show T3 where
show (T3 t) = show (Opaque <$ t)
Or I’ll refactor my data type to derive Show
only for the parts I want to be Show
able by default, and override the other parts:
data T4 = T4
{ f :: X -> Y
, xys :: T4' -- Move the other fields into another type.
}
instance Show T4 where
show (T4 f xys) = "T4 <function> " <> show xys
data T4' = T4'
{ xs :: [String]
, ys :: [Bool]
}
deriving (Show) -- Derive ‘Show’ for the showable fields.
Or if my type is small, I’ll use a newtype
instead of data
, and derive Show
via something like OpaqueFunction
:
{-# LANGUAGE DerivingVia #-}
newtype T5 = T5 (X -> Y, [String], [Bool])
deriving (Show) via (OpaqueFunction X Y, [String], [Bool])
You can use the iso-deriving
package to do this for data
types using lenses if you care about keeping the field names / record accessors.
As for Eq
(or Ord
), it’s not a good idea to have an instance that equates values that can be observably distinguished in some way, since some code will treat them as identical and other code will not, and now you’re forced to care about stability: in some circumstance where I have a == b
, should I pick a
or b
? This is why substitutability is a law for Eq
: forall x y f. (x == y) ==> (f x == f y)
if f
is a “public” function that upholds the invariants of the type of x
and y
(although floating-point also violates this). A better choice is something like T4
above, having equality only for the parts of a type that can satisfy the laws, or explicitly using comparison modulo some function at use sites, e.g., comparing someField
.