Cooperative equality

Some years ago, this thread Can we get rid of cooperative equality? - #87 by fommil discussed whether cooperative equality was a good idea, and whether it could and should be removed.

It seemed like a consensus was developing that cooperative equality was a bad idea for Scala (based on e.g. Can we get rid of cooperative equality? - #57 by odersky), and that it should either be removed from Scala 2, or at least not introduced to Scala 3.

I’ve been unable to turn up any more discussion about this, so I’m wondering whether a conclusion was reached? Cooperative equality seems to still be in place in Scala 3, as far as I can tell. Was the idea of removing it dropped, or were the problems with cooperative equality solved in some other way?

Feel free to merge this post into the original thread if it fits better there, wasn’t sure if reviving such an old thread would be frowned upon.

1 Like

relevant: Multiversal Equality

I made/gave a talk on this topic called Equality in Scala, and the relevant section starts maybe around 25 minutes in.

4 Likes

So cooperative equality does still exist, meaning yeah in an erased context like scala.collection.mutable.HashMap then numeric types of different classes can be equal.

Could be worth reviving the discussion.

When you have static types then you can use CanEqual to prevent these comparisons (except that the numeric types are currently not prevented from comparing to each other even with strictEquality mode)

Note that cooperative equality and multiversal equality are not the same. Multiversal equality is about ruling out statically comparisons that make no sense because they would always return false at runtime. Cooperative equality is about making more boxed versions of primitive numeric types equal when their unboxed versions are equal. This requires emitting more complicated and slower code for == and !=.

I still think cooperative equality was a mistake and would be happy to see an effort to roll it back. Scala 3 did not do it, since Scala 3 tried to reduce runtime incompatibilities to the absolute minimum. After all, there were already a lot of other changes, we did not want hard-to-debug runtime changes on top of that.

Cooperative equality will be hard to change since it changes runtime behavior. So it’s not clear what a migration path would look like.

4 Likes

I think Java recently solved a problem that seems somewhat similar, getting people off sun.misc.Unsafe (and other reflective access to JDK internals).

Their solution was to add a new flag allowing these options when Unsafe is accessed:

  • Log on first access, with enough information to tell where in the program the access is from.
  • Log on every access
  • Crash on access
  • Don’t crash or log

Could a similar approach to the one used by Java work here? Maybe BoxesRuntime.equals could have a few behaviors depending on the value of a system property:

  • The default would be to log a warning plus stack trace to console the first time BoxesRuntime.equals is invoked with a numeric type, and the return value differs from Objects.equals (BoxesRuntime would have to do the comparison twice for numeric types when running in this mode).
  • The second value makes BoxesRuntime log these stack traces every time BoxesRuntime.equals disagrees with Objects.equals for a numeric type.
  • The third value makes BoxesRuntime delegate to Objects.equals immediately, disabling cooperative equality.
  • A fourth value disables the extra comparison entirely, so there’s an escape hatch for applications that need to wait for libraries to fix their uses of cooperative equality, or need to delay for other reasons.

With a toggle like this, Scala could run with the log-once default for a few years, then switch to ban-by-default with no full opt out anymore, and finally 3-5 years down the line, the flag and cooperative equality could be removed?

This would help people spot and fix places they rely on cooperative equality, and allows people to migrate off this behavior at their own pace, but also makes the problem visible to everyone. People that can’t address the changed behavior in their applications in the short term would be able to disable the check until they’re ready to deal with it.

2 Likes