Equal protection under Eq law

Hi, I’ve been thinking equality lately, and I wrote ‘equal protection under Eq law’.

Here’s the short version: The relationship given to Int and Long should be exactly the same as the relationship third-party library like Spire can write UInt or Rational with the first-class numeric types.

  • We should make 1 == 1L an error under strictEquality
  • We should allow custom types to participate in constant expression conversion using FromDigits

why?

Besides making the comparison of Int and Long more consistent with what’s available to UInt etc, the benefit for this is making == closer to Any#equals method. We won’t be able to immediately remove cooperative equality, but this opens the possibility to remove cooperative equality down the line if / when strict equality becomes the default mode.

Previous discussion on this topic - Can we get rid of cooperative equality?

7 Likes

As I mentioned on this issue, I think if we removed == and != from Any and implemented them as extension instead, it would have solved all our troubles (and perhaps added others).

6 Likes

If we can drastically redesign == that might solve many problems but as Martin wrote there we need to work under the constraints of keeping the compatibility and interoperability with Scala 2.13.

Making 1 == 1L not compile is a few tweaks to the existing multiversal equality mechanism. Here’s a quick implementation I attempted - https://github.com/lampepfl/dotty/pull/8303

REPL demo looks like this:

scala> 1 == 1L
val res0: Boolean = true

scala> 1 != 1L
val res1: Boolean = false

scala> {
     |   import scala.language.strictEquality
     |   val l = 1L
     |   l == 1
     | }
4 |  l == 1
  |  ^^^^^^
  |  Values of types Long and Int cannot be compared with == or !=

scala> {
     |   import scala.language.strictEquality
     |   1L == 1
     | }
3 |  1L == 1
  |  ^^^^^^^
  |  Values of types Long and Int cannot be compared with == or !=

That would be pretty inconvenient I think. And if I understand 02-object-model correctly, in the future “reference equality” between wrapper classes like Integer will behave like primitive equality, so it might be best to sit and wait for value classes on the JVM before tweaking things even more on our end.

1 Like

The document says:

two inline objects are == if they are of the same type, and each of their fields are pairwise equal according to == for the static type of that field (except for float and double , which compare according to the semantics of Float::equals and Double::equals ). This definition says that two inline objects are equal if and only they are substitutable – we can discern no difference between them.

Valhalla says inline objects are equal if and only if they are substitutable. Using the rule, the Valhalla unboxed int value 1 would no longer == long value 1L.

It’s true that reference to inline types would use the == semantics of the underlying inline types, but it seems like int#== becomes more like Integer#equals in that world.

1 Like

Yeah it’s a bit unclear from the text but it seems extremely unlikely that they would change the semantics such that 1 == 1L would suddenly start returning false.

I clarified this with Brian Goetz a while back (https://twitter.com/eed3si9n/status/1317150368380428292), and it seems like you’re right. As the semantics of ==, it seems like Java treats this as comparison between two Long typed values, after 1 goes through numeric promotion.

Scala 3 has a similar FromDigits mechanism, so what do you think about allowing 1 == 1L, but not a == b?

scala> {
     |   import scala.language.strictEquality
     |   val a = 1
     |   val b = 1L
     |   a == b
     | }
5 |  a == b
  |  ^^^^^^
  |  Values of types Int and Long cannot be compared with == or !=

Personally I would still find that inconvenient, these values definitely can be compared in a meaningful and intuitive way via numeric promotion, and this is the status-quo in Java and Scala, I don’t think removing this convenience would improve the language even if it’s less consistent with user-defined types.

From https://dotty.epfl.ch/docs/reference/dropped-features/weak-conformance-spec.html it seems like Scala 3 drops weak conformance, and effectively adopts Java’s numeric promotion.

inline val b = 33
def f(): Int = b + 1
Array(b, 33, 5.5)      : Array[Double] // b is an inline val
Array(f(), 33, 5.5)    : Array[AnyVal] // f() is not a constant

Removing given eqlNumber instance I think is more consistent with this direction since by the time def ==(...) is being evaluated, two terms should have the same widened types.