Skip to content

Commit 6b429a4

Browse files
committed
Update doc page
1 parent 2ecb3bb commit 6b429a4

File tree

1 file changed

+49
-49
lines changed

1 file changed

+49
-49
lines changed

docs/docs/reference/contextual/multiversal-equality.md

Lines changed: 49 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -24,62 +24,62 @@ the program will still typecheck, since values of all types can be compared with
2424
But it will probably give unexpected results and fail at runtime.
2525

2626
Multiversal equality is an opt-in way to make universal equality
27-
safer. It uses a binary type class `Eql` to indicate that values of
27+
safer. It uses a binary type class `CanEqual` to indicate that values of
2828
two given types can be compared with each other.
2929
The example above would not typecheck if `S` or `T` was a class
30-
that derives `Eql`, e.g.
30+
that derives `CanEqual`, e.g.
3131
```scala
32-
class T derives Eql
32+
class T derives CanEqual
3333
```
34-
Alternatively, one can also provide an `Eql` given instance directly, like this:
34+
Alternatively, one can also provide a `CanEqual` given instance directly, like this:
3535
```scala
36-
given Eql[T, T] = Eql.derived
36+
given CanEqual[T, T] = CanEqual.derived
3737
```
3838
This definition effectively says that values of type `T` can (only) be
3939
compared to other values of type `T` when using `==` or `!=`. The definition
4040
affects type checking but it has no significance for runtime
4141
behavior, since `==` always maps to `equals` and `!=` always maps to
42-
the negation of `equals`. The right hand side `Eql.derived` of the definition
43-
is a value that has any `Eql` instance as its type. Here is the definition of class
44-
`Eql` and its companion object:
42+
the negation of `equals`. The right hand side `CanEqual.derived` of the definition
43+
is a value that has any `CanEqual` instance as its type. Here is the definition of class
44+
`CanEqual` and its companion object:
4545
```scala
4646
package scala
4747
import annotation.implicitNotFound
4848

4949
@implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=")
50-
sealed trait Eql[-L, -R]
50+
sealed trait CanEqual[-L, -R]
5151

52-
object Eql {
53-
object derived extends Eql[Any, Any]
52+
object CanEqual {
53+
object derived extends CanEqual[Any, Any]
5454
}
5555
```
5656

57-
One can have several `Eql` given instances for a type. For example, the four
57+
One can have several `CanEqual` given instances for a type. For example, the four
5858
definitions below make values of type `A` and type `B` comparable with
5959
each other, but not comparable to anything else:
6060

6161
```scala
62-
given Eql[A, A] = Eql.derived
63-
given Eql[B, B] = Eql.derived
64-
given Eql[A, B] = Eql.derived
65-
given Eql[B, A] = Eql.derived
62+
given CanEqual[A, A] = CanEqual.derived
63+
given CanEqual[B, B] = CanEqual.derived
64+
given CanEqual[A, B] = CanEqual.derived
65+
given CanEqual[B, A] = CanEqual.derived
6666
```
67-
The `scala.Eql` object defines a number of `Eql` given instances that together
67+
The `scala.CanEqual` object defines a number of `CanEqual` given instances that together
6868
define a rule book for what standard types can be compared (more details below).
6969

70-
There is also a "fallback" instance named `eqlAny` that allows comparisons
71-
over all types that do not themselves have an `Eql` given. `eqlAny` is defined as follows:
70+
There is also a "fallback" instance named `canEqualAny` that allows comparisons
71+
over all types that do not themselves have a `CanEqual` given. `canEqualAny` is defined as follows:
7272

7373
```scala
74-
def eqlAny[L, R]: Eql[L, R] = Eql.derived
74+
def canEqualAny[L, R]: CanEqual[L, R] = CanEqual.derived
7575
```
7676

77-
Even though `eqlAny` is not declared as `given`, the compiler will still construct an `eqlAny` instance as answer to an implicit search for the
78-
type `Eql[L, R]`, unless `L` or `R` have `Eql` instances
77+
Even though `canEqualAny` is not declared as `given`, the compiler will still construct an `canEqualAny` instance as answer to an implicit search for the
78+
type `CanEqual[L, R]`, unless `L` or `R` have `CanEqual` instances
7979
defined on them, or the language feature `strictEquality` is enabled.
8080

81-
The primary motivation for having `eqlAny` is backwards compatibility.
82-
If this is of no concern, one can disable `eqlAny` by enabling the language
81+
The primary motivation for having `canEqualAny` is backwards compatibility.
82+
If this is of no concern, one can disable `canEqualAny` by enabling the language
8383
feature `strictEquality`. As for all language features this can be either
8484
done with an import
8585

@@ -88,20 +88,20 @@ import scala.language.strictEquality
8888
```
8989
or with a command line option `-language:strictEquality`.
9090

91-
## Deriving Eql Instances
91+
## Deriving CanEqual Instances
9292

93-
Instead of defining `Eql` instances directly, it is often more convenient to derive them. Example:
93+
Instead of defining `CanEqual` instances directly, it is often more convenient to derive them. Example:
9494
```scala
95-
class Box[T](x: T) derives Eql
95+
class Box[T](x: T) derives CanEqual
9696
```
9797
By the usual rules of [type class derivation](./derivation.md),
98-
this generates the following `Eql` instance in the companion object of `Box`:
98+
this generates the following `CanEqual` instance in the companion object of `Box`:
9999
```scala
100-
given [T, U](using Eql[T, U]) as Eql[Box[T], Box[U]] = Eql.derived
100+
given [T, U](using CanEqual[T, U]) as CanEqual[Box[T], Box[U]] = CanEqual.derived
101101
```
102102
That is, two boxes are comparable with `==` or `!=` if their elements are. Examples:
103103
```scala
104-
new Box(1) == new Box(1L) // ok since there is an instance for `Eql[Int, Long]`
104+
new Box(1) == new Box(1L) // ok since there is an instance for `CanEqual[Int, Long]`
105105
new Box(1) == new Box("a") // error: can't compare
106106
new Box(1) == 1 // error: can't compare
107107
```
@@ -112,31 +112,31 @@ The precise rules for equality checking are as follows.
112112

113113
If the `strictEquality` feature is enabled then
114114
a comparison using `x == y` or `x != y` between values `x: T` and `y: U`
115-
is legal if there is a `given` of type `Eql[T, U]`.
115+
is legal if there is a `given` of type `CanEqual[T, U]`.
116116

117117
In the default case where the `strictEquality` feature is not enabled the comparison is
118118
also legal if
119119

120120
1. `T` and `U` are the same, or
121121
2. one of `T`, `U` is a subtype of the _lifted_ version of the other type, or
122-
3. neither `T` nor `U` have a _reflexive_ `Eql` instance.
122+
3. neither `T` nor `U` have a _reflexive_ `CanEqual` instance.
123123

124124
Explanations:
125125

126126
- _lifting_ a type `S` means replacing all references to abstract types
127127
in covariant positions of `S` by their upper bound, and replacing
128128
all refinement types in covariant positions of `S` by their parent.
129-
- a type `T` has a _reflexive_ `Eql` instance if the implicit search for `Eql[T, T]`
129+
- a type `T` has a _reflexive_ `CanEqual` instance if the implicit search for `CanEqual[T, T]`
130130
succeeds.
131131

132-
## Predefined Eql Instances
132+
## Predefined CanEqual Instances
133133

134-
The `Eql` object defines instances for comparing
134+
The `CanEqual` object defines instances for comparing
135135
- the primitive types `Byte`, `Short`, `Char`, `Int`, `Long`, `Float`, `Double`, `Boolean`, and `Unit`,
136136
- `java.lang.Number`, `java.lang.Boolean`, and `java.lang.Character`,
137137
- `scala.collection.Seq`, and `scala.collection.Set`.
138138

139-
Instances are defined so that every one of these types has a _reflexive_ `Eql` instance, and the following holds:
139+
Instances are defined so that every one of these types has a _reflexive_ `CanEqual` instance, and the following holds:
140140

141141
- Primitive numeric types can be compared with each other.
142142
- Primitive numeric types can be compared with subtypes of `java.lang.Number` (and _vice versa_).
@@ -152,7 +152,7 @@ Instances are defined so that every one of these types has a _reflexive_ `Eql` i
152152

153153
## Why Two Type Parameters?
154154

155-
One particular feature of the `Eql` type is that it takes _two_ type parameters, representing the types of the two items to be compared. By contrast, conventional
155+
One particular feature of the `CanEqual` type is that it takes _two_ type parameters, representing the types of the two items to be compared. By contrast, conventional
156156
implementations of an equality type class take only a single type parameter which represents the common type of _both_ operands.
157157
One type parameter is simpler than two, so why go through the additional complication? The reason has to do with the fact that, rather than coming up with a type class where no operation existed before,
158158
we are dealing with a refinement of pre-existing, universal equality. It is best illustrated through an example.
@@ -174,39 +174,39 @@ does not work, since it refers to the covariant parameter `T` in a nonvariant co
174174
```
175175
This generic version of `contains` is the one used in the current (Scala 2.13) version of `List`.
176176
It looks different but it admits exactly the same applications as the `contains(x: Any)` definition we started with.
177-
However, we can make it more useful (i.e. restrictive) by adding an `Eql` parameter:
177+
However, we can make it more useful (i.e. restrictive) by adding a `CanEqual` parameter:
178178
```scala
179-
def contains[U >: T](x: U)(using Eql[T, U]): Boolean // (1)
179+
def contains[U >: T](x: U)(using CanEqual[T, U]): Boolean // (1)
180180
```
181181
This version of `contains` is equality-safe! More precisely, given
182182
`x: T`, `xs: List[T]` and `y: U`, then `xs.contains(y)` is type-correct if and only if
183183
`x == y` is type-correct.
184184

185-
Unfortunately, the crucial ability to "lift" equality type checking from simple equality and pattern matching to arbitrary user-defined operations gets lost if we restrict ourselves to an equality class with a single type parameter. Consider the following signature of `contains` with a hypothetical `Eql1[T]` type class:
185+
Unfortunately, the crucial ability to "lift" equality type checking from simple equality and pattern matching to arbitrary user-defined operations gets lost if we restrict ourselves to an equality class with a single type parameter. Consider the following signature of `contains` with a hypothetical `CanEqual1[T]` type class:
186186
```scala
187-
def contains[U >: T](x: U)(using Eql1[U]): Boolean // (2)
187+
def contains[U >: T](x: U)(using CanEqual1[U]): Boolean // (2)
188188
```
189189
This version could be applied just as widely as the original `contains(x: Any)` method,
190-
since the `Eql1[Any]` fallback is always available! So we have gained nothing. What got lost in the transition to a single parameter type class was the original rule that `Eql[A, B]` is available only if neither `A` nor `B` have a reflexive `Eql` instance. That rule simply cannot be expressed if there is a single type parameter for `Eql`.
190+
since the `CanEqual1[Any]` fallback is always available! So we have gained nothing. What got lost in the transition to a single parameter type class was the original rule that `CanEqual[A, B]` is available only if neither `A` nor `B` have a reflexive `CanEqual` instance. That rule simply cannot be expressed if there is a single type parameter for `CanEqual`.
191191

192192
The situation is different under `-language:strictEquality`. In that case,
193-
the `Eql[Any, Any]` or `Eql1[Any]` instances would never be available, and the
193+
the `CanEqual[Any, Any]` or `CanEqual1[Any]` instances would never be available, and the
194194
single and two-parameter versions would indeed coincide for most practical purposes.
195195

196-
But assuming `-language:strictEquality` immediately and everywhere poses migration problems which might well be unsurmountable. Consider again `contains`, which is in the standard library. Parameterizing it with the `Eql` type class as in (1) is an immediate win since it rules out non-sensical applications while still allowing all sensible ones.
196+
But assuming `-language:strictEquality` immediately and everywhere poses migration problems which might well be unsurmountable. Consider again `contains`, which is in the standard library. Parameterizing it with the `CanEqual` type class as in (1) is an immediate win since it rules out non-sensical applications while still allowing all sensible ones.
197197
So it can be done almost at any time, modulo binary compatibility concerns.
198-
On the other hand, parameterizing `contains` with `Eql1` as in (2) would make `contains`
199-
unusable for all types that have not yet declared an `Eql1` instance, including all
198+
On the other hand, parameterizing `contains` with `CanEqual1` as in (2) would make `contains`
199+
unusable for all types that have not yet declared a `CanEqual1` instance, including all
200200
types coming from Java. This is clearly unacceptable. It would lead to a situation where,
201-
rather than migrating existing libraries to use safe equality, the only upgrade path is to have parallel libraries, with the new version only catering to types deriving `Eql1` and the old version dealing with everything else. Such a split of the ecosystem would be very problematic, which means the cure is likely to be worse than the disease.
201+
rather than migrating existing libraries to use safe equality, the only upgrade path is to have parallel libraries, with the new version only catering to types deriving `CanEqual1` and the old version dealing with everything else. Such a split of the ecosystem would be very problematic, which means the cure is likely to be worse than the disease.
202202

203203
For these reasons, it looks like a two-parameter type class is the only way forward because it can take the existing ecosystem where it is and migrate it towards a future where more and more code uses safe equality.
204204

205205
In applications where `-language:strictEquality` is the default one could also introduce a one-parameter type alias such as
206206
```scala
207-
type Eq[-T] = Eql[T, T]
207+
type Eq[-T] = CanEqual[T, T]
208208
```
209-
Operations needing safe equality could then use this alias instead of the two-parameter `Eql` class. But it would only
209+
Operations needing safe equality could then use this alias instead of the two-parameter `CanEqual` class. But it would only
210210
work under `-language:strictEquality`, since otherwise the universal `Eq[Any]` instance would be available everywhere.
211211

212212

0 commit comments

Comments
 (0)