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 |