Eq1, Ord1, Show1: move from eq1, compare1, showsPrec1 to liftEq, liftCompare, liftShowsPrec

Ryan Scott ryan.gl.scott at gmail.com
Mon Jan 11 15:00:44 UTC 2016


> In transformers-0.5 it seems that I have to implement Eq1, Ord1, Show1 instances manually.

For better or worse, that is indeed the case. Before,
Eq1/Ord1/Read1/Show1 relied on the type argument having an implicit
dictionary to use, which is why you could get away with piggybacking
off of your existing Eq/Ord/Read/Show instances. Now, Eq1, Eq2, and
friends rely on explicit dictionary passing, so they're strictly more
powerful than Eq et al.

> Is there some assistance to define eq1 and compare1 for an ADT?

At the moment, no. Sometime in the future, I want to do two things
which should make the situation a little better:

1. Write a language extension to derive Eq1/Eq2/etc. It definitely
won't be making it into GHC 8.0 due to time constraints, plus I need
to propose a design. And...
2. ...I should write a Template Haskell shim which implements the
proposed design. I already have a package called deriving-compat [1]
which has done this (but only for recent changes to -XDeriveFoldable),
so that would probably be a logical place to put it.

Until then, if you need to hand-roll Eq1/Eq2/etc. instances that align
with your Eq instances, I'd recommend copying the output of ghc
-ddump-deriv, copying the generated instances, and replacing the
necessary calls with dictionary arguments. For example, given this
instance:

    data Example a = Example Int a deriving Eq

Compiling with ghc -ddump-deriv gives you this (after some code cleanup):

    instance Eq a => Eq (Example a) where
      Example i1 a1 == Example i2 a2 = i1 == i2 && a1 == a2
      e1 /= e2 = not (e1 == e2)

Then, you can create an Eq1 instance with some minor adjustments:

    instance Eq1 Example where
      liftEq eq (Example i1 a1) (Example i2 a2) = i1 == i2 && eq a1 a2

> What are the use cases where eq1 was not powerful enough and liftEq is needed?

The previous type signature of Data.Functor.Classes in
transformers-0.4 resulted in some awkward instances, a prime example
being found in Data.Functor.Compose:

    import Data.Functor.Classes(Eq1(..))

    newtype Compose f g a = Compose (f (g a))
    instance (Functor f, Eq1 f, Eq1 g, Eq a) => Eq (Compose f g a) where
      Compose x == Compose y = eq1 (fmap Apply x) (fmap Apply y)
    instance (Functor f, Eq1 f, Eq1 g) => Eq1 (Compose f g) where eq1 = (==)

    newtype Apply g a = Apply (g a)
    instance (Eq1 g, Eq a) => Eq (Apply g a) where
        Apply x == Apply y = eq1 x y

This is a super-kludgey instance because it requires f to be a Functor
instance. In general, having these Functor constraints pop up
everywhere results in less efficient instances, and it won't work at
all for many GADT-like constructions that can't have legal Functor
instances. With the new API, there is no need for these Functor
constraints:

    import Data.Functor.Classes(Eq1(..))

    newtype Compose f g a = Compose (f (g a))
    instance (Eq1 f, Eq1 g) => Eq1 (Compose f g) where
      liftEq eq (Compose x) (Compose y) = liftEq (liftEq eq) x y

Ryan S.
-----
[1] http://hackage.haskell.org/package/deriving-compat


More information about the Libraries mailing list