Skip to content

Proposal: Remove method (/=) from class Eq #3

Description

@nomeata

@Bodigrim suggested I take the proposal that I recently submitted to libraries@ here, to play guinea pig for this process.

What

I suggest to change

class Eq a where (==), (/=) :: a -> a -> Bool

to

class Eq a where (==) :: a -> a -> Bool

and turn (/=) into a normal function.

Why

  • If we’d define class Eq now, we wouldn’t include (/=).

    In general, the motivations for “redundant” methods with default implementation could be

    • The alternative method can be implemented more efficiently than the “main” method.
    • If the “main” method has a default implementation, too: The alternative method is easier or more natural to implement.

    Here, neither of these apply (justification below), and “fixing” this is worth it, for the following reasons:

  • Teaching: The Eq class is often the first, or one of the first, type classes that beginners will face. This means that educators have to explain what default methods are and why they exist, and then have to apologetically say “but this doesn't really apply here, sorry”. (The Ord class is much better in that regard, for example (<=) can be implemented more efficiently than compare.)

  • Development cost and effort: Every developer implementing an Eq instance will have to make a decision whether to instantiate (/=). They have more documentation to read. And precisely because (/=) doesn’t really make sense here, they might have to think extra long about whether to instantiate (/=) or not.

    Removing the method will save some time of future developers.

  • Similarly. whenever someone reads code and comes across an Eq instance with (/=) defined explicitly they have extra work to do, and think “Why was this defined?”, “Is it lawful?”. Removing the method from the class alltogether will mean library code gets simpler and easier to read.

    Removing the method will cause libraries to have less code, not more.

  • Lawfulness. Eq says x /= y = not (x == y), but that is only true if everybody plays by the rules. By having (/=) it is possible for instances to be unlawful (intentionally or accidentally), for little gain.

    Removing the method will guarantee that equation.

  • Code changes are backward compatible. This change will require some library maintainers to change their code. But the code change is simple (remove the method, possibly adjust the import statement), and is compatible with old versions of base. No complex migration strategy, no CPP needed.

  • Performance gains (very minor). Single method classes are currently represented more efficiently in GHC. In some higher-order cases removing the method might speed up programs. Also, compiling base and libraries with Eq instances and creating haddocks gets (very slightly) faster, because there is less to do.

  • Implementation in base is simple: See https://gitlab.haskell.org/ghc/ghc/-/merge_requests/6793.

Why not (and rebuttals)

Most of these are taken from the libraries thread; rebuttals are mine.

  • (/=) can sometimes be implemented more efficiently.

    Rebuttal: Beyond the cost of a single call to not, which GHC often optimizes away anyways, this is not possible (for law-abiding instances.)

  • (/=) can sometimes be more natural to implement.

    Rebuttal: Relatively unlikely, and possibly more error-prone and harder to read, due to the many negations.

  • There is a one-time cost for affected library authors, and it’s not worth it.

    A mildly naive hackage search shows 131 affected packages.

    Rebuttal: The change is easy, mechanical and backward compatible. But more important: Because this change simplifies both base and the code of affected libraries, it will pay off eventually, because of all the work we (as the community) save over time by no longer having to think about (/=). In fact, the evidence that 131 packages’s authors manually (and, in most cases, pointlessy) wrote an (/=) means this proposal will save the next 131 packages’s authors, and beyond, unnecessary work.

  • Some libraries use this to write unlawful instances, e.g. Non-lawful Eq Null instance? haskellari/postgresql-simple#78.

    Rebuttal: Don’t write unlawful instances. If you need (/=) to behave differently than the negation of (==), Eq is the wrong type class. I understand that this is a bit harsh, as the libraries authors might have reasons to break the lawfulness.

  • RULE matching on single method class methods doesn't work reliably, but does work for other classes.

    _ Rebuttal_: I consider that a bug in GHC and would fix one way or another before enacting this proposal.

  • We should clean up the type class hierarchy in a bigger way.

    Rebuttal: Maybe, but that should not doing this small cleanup proposed here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    approvedApproved by CLC voteawaits-mergeApproved, but MR is still unmerged

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions