You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Mar 27, 2025. It is now read-only.
But Scala 2 universal equality reduces type-safety in some cases
Universal Equality means that '==' and '!=' will compare any two values no matter their types
This can lead to bugs, typically after refactorings - even refactorings that are just about changing types
"This is a real worry in practice. I recently abandoned a desirable extensive refactoring because I feared that it would be too hard to track down such errors."
...which we refactor to use UUID as the 'id' type...
importjava.util.UUIDfinalcaseclassId(value: Long) extendsAnyVal// We keep because used elsewhere in our projectfinalcaseclassItem(id: UUID)
...but we forget to change the Repository...
classRepository(items: Seq[Item]):deffindById(id: Id):Option[Item] = {
// The following type-checks but will always return false// so we never find any items
items.find(_.id == id) // Comparing an `id` with a `UUID`
}
Multiversal Equality: Typeclass approach
Attempts to improve equality checking in Scala Community go back at least to 2010
Scalaz library first introduced the Equal typeclass, inspired by the Eq typeclass in Haskell
Scala 3 also adopts this typeclass approach:
"Ultimately it's the developer who [is] best placed to characterize which equalities make sense. ... The best known way to characterize such relationships is with type classes"
@implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=")
sealedtraitCanEqual[-L, -R]
To compare two values of the two types L and R the compiler searches for an CanEqual instance linking the two types
By default (when multiversal equality is not enabled), the compiler falls back to default CanEqual[Any, Any] instance
Is 'opt-in' to keep backwards compatibility
Either locally with import: import scala.language.strictEquality
Or globally with compiler option: -language:strictEquality
Backward Compatibility: Warning
WARNING: Even without opting-in, Scala 3 equality comparison isn't exactly backwards compatible with Scala 2
Scala 2:
scala>Seq(1) ==Set(1)
res0:Boolean=false
Scala 3:
scala>Seq(1) ==Set(1)
1|Seq(1) ==Set(1)
|^^^^^^^^^^^^^^^^|Values of types Seq[Int] and Set[Int] cannot be compared with== or !=
Multiversal Equality: Example I
Revisiting our Item refactoring example...
importjava.util.UUIDfinalcaseclassId(value: Long) extendsAnyVal// We keep because used elsewhere in our projectfinalcaseclassItem(id: UUID)
...forgetting to change the Repository will produce a type error (if we opt-in to multiversal equality)
importscala.language.strictEqualityclassRepository(items: Seq[Item]):deffindById(id: Id):Option[Item] = {
items.find(_.id == id)
^^^^^^^^^^Values of types java.util.UUID and Id cannot be compared with== or !=
}
Multiversal Equality: Example II
So we realise our mistake can we change 'Id' to 'UUID' in the Repository method
importscala.language.strictEqualityclassRepository(items: Seq[Item]):deffindById(id: UUID):Option[Item] = {
items.find(_.id == id)
^^^^^^^^^^Values of types java.util.UUID and java.util.UUID cannot be compared with== or !=
}
Even though the type is correct, we need to tell the compiler that the type can be compared for equality. We need an CanEqual[UUID, UUID] instance
NOTE: Out of the box, Scala 3 provides reflexive CanEqual instances for each of:
The easiest way to provide new CanEqual instances is to use Typeclass Derivation mechanism available in Scala 3
Scala 3 built-in typeclasses like CanEqual and Ordering provide a derived method for generating new typeclass instances of types defined elsewhere (like UUID)
"The scheme effectively leads to a partition of the former universe of types into sets of types. Values with types in the same partition can be compared among themselves but values with types in different partitions cannot ... "
Providing / deriving a new CanEqual instances creates a new partition
"...So instead of a single universe of values that can be compared to each other we get a multiverse of partitions. Hence the name of the proposal: Multiversal Equality"
Multiversal Equality
In this exercise, we will see how to 'opt-in' to Multiversal Equality and how to create an CanEqual Type class instance
Make sure you're positioned at exercise "multiversal equality"
Follow the exercise instructions provided in the README.md file in the code folder