Skip to content

Proposal: Relax instances for Functor combinators; put superclasses on <class>1 to make less-breaking  #10

Closed
@Ericson2314

Description

@Ericson2314

Implementation https://gitlab.haskell.org/ghc/ghc/-/merge_requests/4727

The first change makes the Eq, Ord, Show, and Read instances for Sum, Product, and Compose match those for :+:, :*:, and :.:. These have the proper flexible contexts that are exactly what the instance needs:

For example, instead of

instance (Eq1 f, Eq1 g, Eq a) => Eq (Compose f g a) where
  (==) = eq1

we do

deriving instance Eq (f (g a)) => Eq (Compose f g a)

But, that change alone is rather breaking, because until now Eq (f a) and Eq1 f (and respectively the other classes and their *1 equivalents too) are incomparable constraints. This has always been an annoyance of working with the *1 classes, and now it would rear it's head one last time as an pesky migration.

Instead, we give the *1 classes superclasses, like so:

(forall a. Eq a => Eq (f a)) => Eq1 f

along with some laws that canonicity is preserved, like:

liftEq (==) = (==)

and likewise for *2 classes

(forall a. Eq a => Eq1 (f a)) => Eq2 f

along with some laws that canonicity is preserved, like:

liftEq2 (==) = liftEq1

The *1 classes also have default methods using the *2 classes where possible.

What this means, as explained in the docs in my implementation, is that *1 classes really are generations of the regular classes, indicating that the methods can be split into a canonical lifting combined with a canonical inner, with the super class "witnessing" the laws[1] in a fashion.

Circling back to the pragmatics of migrating, note that the superclass means evidence for the old Sum, Product, and Compose instances is (more than) sufficient, so breakage is less likely --- as long no instances are "missing", existing polymorphic code will continue to work.

Breakage can occur when a datatype implements the *1 class but not the corresponding regular class, but this is almost certainly an oversight. For example, containers made that mistake for Tree and Ord, which I fixed in haskell/containers#761, but fixing the issue by adding Ord1 was extremely uncontroversial.


[1]: In fact, someday, when the laws are part of the language and not
only documentation, we might be able to drop the superclass field of the
dictionary by using the laws to recover the superclass in an
instance-agnostic manner, e.g. with a non-overloaded function with
type:

DictEq1 f -> DictEq a -> DictEq (f a)

But I don't wish to get into optomizations now, just demonstrate the
close relationship between the law and the superclass.

Metadata

Metadata

Assignees

No one assigned

    Labels

    approvedApproved by CLC votebase-4.18Implemented in base-4.18 (GHC 9.6)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions