|
| 1 | +--- |
| 2 | +layout: doc-page |
| 3 | +title: "Multiversal Equality" |
| 4 | +--- |
| 5 | + |
| 6 | +Previously, Scala had universal equality: Two values of any types |
| 7 | +could be compared with each other with `==` and `!=`. This came from |
| 8 | +the fact that `==` and `!=` are implemented in terms of Java's |
| 9 | +`equals` method, which can also compare values of any two reference |
| 10 | +types. |
| 11 | + |
| 12 | +Universal equality is convenient but also dangerous since it |
| 13 | +undermines type safety. Say you have an erroneous program where |
| 14 | +a value `y` has type `S` instead of the expected type `T`. |
| 15 | + |
| 16 | + val x = ... // of type T |
| 17 | + val y = ... // of type S, but should be T |
| 18 | + x == y // typechecks, will always yield false |
| 19 | + |
| 20 | +If all you do with `y` is compare it to other values of type `T`, the program will |
| 21 | +typecheck but probably give unexpected results. |
| 22 | + |
| 23 | +Multiversal equaliy is an opt-in way to make universal equality |
| 24 | +safer. The idea is that by declaring an `implicit` value one can |
| 25 | +restrict the types that are legal in comparisons. The example above |
| 26 | +would not typecheck if an implicit was declared like this for type `T` |
| 27 | +(or an analogous one for type `S`): |
| 28 | + |
| 29 | + implicit def eqT: Eq[T, T] = Eq |
| 30 | + |
| 31 | +This definition effectively says that value of type `T` can (only) be |
| 32 | +compared with `==` or `!=` to other values of type `T`. The definition |
| 33 | +is used only for type checking; it has no significance for runtime |
| 34 | +behavior, since `==` always maps to `equals` and `!=` alwatys maps to |
| 35 | +the negation of `equals`. The right hand side of the definition is a value |
| 36 | +that has any `Eq` instance as its type. Here is the definition of class |
| 37 | +`Eq` and its companion object: |
| 38 | + |
| 39 | + package scala |
| 40 | + import annotation.implicitNotFound |
| 41 | + |
| 42 | + @implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=") |
| 43 | + sealed trait Eq[-L, -R] |
| 44 | + |
| 45 | + object Eq extends Eq[Any, Any] |
| 46 | + |
| 47 | +One can have several `Eq` instances for a type. For example, the four |
| 48 | +definitions below make values of type `A` and type `B` comparable with |
| 49 | +each other, but not comparable to anything else: |
| 50 | + |
| 51 | + implicit def eqA : Eq[A, A] = Eq |
| 52 | + implicit def eqB : Eq[B, B] = Eq |
| 53 | + implicit def eqAB: Eq[A, B] = Eq |
| 54 | + implicit def eqBA: Eq[B, A] = Eq |
| 55 | + |
| 56 | +(As usual, the names of the implicit definitions don't matter, we have |
| 57 | +chosen `eqA`, ..., `eqBA` only for illustration). |
| 58 | + |
| 59 | +The `dotty.DottyPredef` object defines a number of `Eq` |
| 60 | +implicits. `dotty.DottyPredef` is a temporary `Predef`-like object. |
| 61 | +The contents of this object are by default imported into every |
| 62 | +program. Once dotty becomes standard Scala, `DottyPredef` will go away |
| 63 | +and its contents will be merged with `scala.Predef`. |
| 64 | + |
| 65 | +The `Eq` instances defined by `DottyPredef` make values of types |
| 66 | +`String`, `Boolean` and `Unit` only comparable to values of the same |
| 67 | +type. They also make numbers only comparable to other numbers, and |
| 68 | +sequences only comparable to other sequences. There's also a |
| 69 | +"fallback" instance `eqAny` that allows comparisons over types that do |
| 70 | +not themeselves have an `Eq` instance. `eqAny` is defined as follows: |
| 71 | + |
| 72 | + implicit def eqAny[L, R]: Eq[L, R] = Eq |
| 73 | + |
| 74 | +The primary motivation for having `eqAny` is backwards compatibility, |
| 75 | +if this is of no concern one can disable `eqAny` by unimporting it |
| 76 | +from `DottyPredef` like this |
| 77 | + |
| 78 | + import dotty.DottyPredef.{eqAny => _, _} |
| 79 | + |
| 80 | +All `enum` types also come with `Eq` instances that make values of the |
| 81 | +`enum` type comparable only to other values of that `enum` type. |
| 82 | + |
| 83 | +The precise rules for equality checking are as follows. |
| 84 | + |
| 85 | + 1. A comparison using `x == y` or `x != y` between values `x: T` and `y: U` |
| 86 | + is legal if either `T` and `U` are the same, or one of the types is a subtype |
| 87 | + of the other, or an implicit value of type `scala.Eq[T, U]` is found. |
| 88 | + |
| 89 | + 2. The usual rules for implicit search apply also to `Eq` instances, |
| 90 | + with one modification: The value `eqAny` in `dotty.DottyPredef` is |
| 91 | + eligible only if neither `T` nor `U` have a reflexive `Eq` |
| 92 | + instance themselves. Here, a type `T` has a reflexive `Eq` |
| 93 | + instance if the implicit search for `Eq[T, T]` where `eqAny` is |
| 94 | + not eligible is successful. |
| 95 | + |
| 96 | +More on multiversal equality is found in a [blog post] |
| 97 | +and a [Github issue]. |
0 commit comments