Search code examples
purescript

Instance of Eq with and without Typeclass Constraints


Here is an implementation of Eq for my algebraic data type (ADT)

data Stateful a =
  Advancing a
  | Stable a
  | Finished a

instance statefulEq :: (Eq a) => Eq (Stateful a)
  where
    eq (Advancing x) (Advancing y) = eq x y
    eq (Stable x) (Stable y) = eq x y
    eq (Finished x) (Finished y) = eq x y
    eq _ _ = false

What this says (I hope) is that Stateful has an instance of Eq if its element has an instance of Eq

Is there a way I can change this to allow for second/backup implementation as well? In SudoCode pseudocode:

instance statefulEq :: Eq (Stateful a)
  where
    eq (Advancing (Eq x)) (Advancing (Eq y)) = eq x y
    eq (Advancing x) (Advancing y) = true
...

A way to say: if the elements have an instance of Eq, use that, otherwise return true.

or if I had some function isConstrained :: forall a. TypeClass -> a -> Boolean

then perhaps

instance statefulEq :: Eq (Stateful a)
  where
    eq (Advancing x) (Advancing y)
      | isConstrained Eq x && isConstrained Eq x = eq x y
      | otherwise = true
...

Solution

  • No, you cannot do this, and there is a good reason for it.

    You see, the constraints don't exist universally, they have to be "in scope", which basically means imported from a module in which they're defined.

    And this means that the Eq constraint for your type can be either in scope or not, and your program behavior will depend on whether the constraint is in scope. In plainer terms, adding a seemingly unrelated module import may change your program behavior without any warning.

    This sort of "action at a distance" is a big enough source of bugs in practice that Haskell and PureScript explicitly disallow it.


    Also, a side note: your implementation of Eq (Stateful a) is 100% equivalent to the automatically derived one. You could replace the whole thing with:

    derive instance statefulEq :: Eq a => Eq (Stateful a)