Home > Software engineering >  Scala 3 multiversal equality of type parameters
Scala 3 multiversal equality of type parameters

Time:12-09

In Scala 3, with -language:strictEquality, this code:

trait X[A]:
    def val1: A
    def val2: A
    def f() =
        if val1 == val2 then
            println("Same")

produces this error:

Values of types A and A cannot be compared with == or !=

I looked for some trait that would tell the compiler that A can be compared to itself, but couldn't find one. The solution I found was to add this line:

given CanEqual[A, A] = CanEqual.derived

However, I still have several questions:

  1. How come you can't compare two instances of the same type? What are some instances of A where val1 == val2 would not be legal?
  2. Is there a trait that would add ==, != to a type?
  3. If the compiler can't figure out that you can compare A to A when A is a type parameter, then how come given CanEqual[A, A] = CanEqual.derived keeps it happy? What is the magic in CanEqual.derived and why can't it be applied by default when using ==?

CodePudding user response:

  1. This comparison is not really illegal, it is just undefined.

  2. The given solution is more flexible than a trait, but you can just define == on your type if you prefer.

  3. You have explicitly told the compiler that you don't want it to generate a default == operator. That's what strictEquality means.

The logic behind strictEquality is that there are different kinds of equality and the compiler shouldn't arbitrarily chose one kind. Objects can be compared by reference or by contents, and it neither is "correct" in every case.

Without strictEquality the compiler implements referential equality by default, which means two values are the same only if they refer to the same object. case class implements structural equality by checking each field for equality, so two different instances are "equal" if all the values of all the fields are the same. Other classes can implement equality by comparing only a relevant subset of field, or by other criteria.

According to the documentation strict equality is opt-in for for compatibility and migration reasons.

CodePudding user response:

Without -language:strictEquality, these == calls are actually handled by equals(Object other) method defined on Java Object class.

So, it used either the overridden equals method for your class or the defualt equals from Object class. This often results in unexpected behaviours.

Adding -language:strictEquality changes this behaviour, and == are handled using the typeclass CanEqual[A, A].

To ensure that you are using correct equality, the design decision was to force you to explicitly provide that CanEqual[A, A].

Now, there is no default catch all way to compare two values. If you want anything to be compared, be ready to provide how to comapre as well.

The comparision is not illegal in any way, but the language just makes no assumptions on how two values should be compared.

  • Related