You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@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.
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.
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.
@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
to
and turn
(/=)into a normal function.Why
If we’d define
class Eqnow, we wouldn’t include(/=).In general, the motivations for “redundant” methods with default implementation could be
Here, neither of these apply (justification below), and “fixing” this is worth it, for the following reasons:
Teaching: The
Eqclass 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”. (TheOrdclass is much better in that regard, for example(<=)can be implemented more efficiently thancompare.)Development cost and effort: Every developer implementing an
Eqinstance 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
Eqinstance 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.
Eqsaysx /= 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
importstatement), and is compatible with old versions ofbase. 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
baseand libraries withEqinstances and creating haddocks gets (very slightly) faster, because there is less to do.Implementation in
baseis simple: See https://gitlab.haskell.org/ghc/ghc/-/merge_requests/6793.Why not (and rebuttals)
Most of these are taken from the
librariesthread; 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
baseand 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(==),Eqis 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.