martin odersky | 7 May 11:56 2011

Rethinking equality

Now that 2.9 is almost out the door, we have the luxury to think of what could come next. One thing I would like to address is equality. The current version did not age well; the longer one looks at it, the uglier it gets. In particular it is a great impediment for DSL design. Witness the recent popularity of === as an alternative equality operator. I previously thought we were stuck with Java-like universal equality for backwards compatibility reasons. But there might be a workable way out of that morass. It works in three steps, which would coincide with the next three major revisions of Scala (yes, sometimes progress has to be slow!)

Step 1:

Introduce the standard Equals type class:

class Equals[T] {
  def eql(x: T, y: T)

And put the following method in Predef:

  <at> inline def areEqual[T](x: T, y: T)(implicit eq: Equals[T]) = eq.eql(x, y)

Define equality as follows:

  X == Y  is equivalent to areEqual(X, Y), if that typechecks, and otherwise equivalent to what it was until now (i.e. universal equality)

[Aside: I note that the spec still defines Any.== to be

   if (null eq this) null eq that else this equals that

We should update that to reflect the realities wrt boxed numbers (on the other hand, if we continue down the path I outline, those realities will also change, see below).]

Furthermore, issue a warning if there is an Equals[T] class for one of the types of X and Y, but not the other (in that case we fall back to the default behavior).

The effect of step 1 is just that any implicit Equals definitions override default behavior, so we are backwards compatible.

Step 2:

Define equality as follows:

  X == Y  is equivalent to areEqual(X, Y), if there is an implicit Equals[T] value for at least one of the types T of X or Y.

Otherwise it is equivalent to universal equality, but in that case a warning is issued.

Step 3:

Define equality as follows:

  X == Y  is equivalent to areEqual(X, Y).

Here's an example: Let's say we have a class Person with an implicit Equals[Person].
Let's assume:

  p, q: Person
  a, b: Any

Then we have

           Step1           Step2              Step3
  p == q   implicit        implicit           implicit

  p == a      universal       error              error
  a == p   + warning

  a == b   universal       universal          error
                           + warning


1. Of course I assume everywhere that X != Y is !(X == Y).
2. Once we have arrived at step 3, the universal equality logic for boxed numbers would go into
the implicit equalities for these numbers.
3. All logic we have now in the compiler that warns of equalities that are always true or false is no longer needed.