haskellderivingderivingvia

Deriving Eq and Show for an ADT that contains fields that can't have Eq or Show


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.


Solution

  • 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 Showing 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 Showable 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.