Learn myself some Scala 3, episode 4: strict equality
In Scala 2 – like in Java – we have universal equality which means that we can compare two objects – each of any type – for equality:
1 | scala> final case class Foo() |
As shown above, universal equality can lead to pointless equality comparisons. The reason for this unfortunate behavior is the equals method, which is defined for every class – either inherited from Any or overwritten – and has a parameter of type Any:
1 | def equals(other: Any): Boolean |
Scala 3 adds the strictEquality language feature which only allows using == and its counterpart != in the presence of a given Eql instance:
1 | scala> import scala.language.strictEquality |
As we can see above, after activating strict equality – also called multiversal equality – we can no longer compare two Foo instances for equality. After providing a given Eql[Foo, Foo] instance this becomes possible, but we still cannot perform the pointless comparison of Foo() and Option(Foo()), which is exactly what we want in most cases (see below).
Instead of “manually” providing given Eql[A, A] instances to compare certain types A with themselves for equality, we can simply derive them:
1 | scala> final case class Bar() derives Eql |
It is worth noting, that Eql is not used at runtime – the actual equality comparison is still carried out through calling good old equals.
Also noteworthy, we can still compare objects of different type for equality. All we need to do is provide the necessary given Eql instances – we need two, one for each “direction”:
1 | scala> given Eql[Foo, Bar] = Eql.derived |
The Scala standard library provides some of these bidirectional Eql instances for numeric types:
1 | scala> 1L == 1 |
If backwards compatibility is needed, we simply not use the strictEquality language feature. This leads to the compiler providing the needed given Eql instance – if necessary – by using the eqlAny method, which itself is not defined as given:
1 | def eqlAny[L, R]: Eql[L, R] = Eql.derived |