lampepfl / dotty-feature-requests

Historical feature requests. Please create new feature requests at https://github.com/lampepfl/dotty/discussions/new?category=feature-requests
31 stars 2 forks source link

Proposal: implement multiversal equality using extension methods #52

Open soronpo opened 5 years ago

soronpo commented 5 years ago

Dotty has advanced us to Multiversal Equality, which is great, but I feel can be better now that we have Extension Methods in the language.

Problem: Say I wish to use the == operator to extend a class with a new comparison.

class Foo
class Bar
import scala.language.strictEquality
def (f : Foo) == (b : Bar) : Boolean = {
  println("It's time for something completely different")
  false
}
val result = new Foo == new Bar

Currently we get the error Values of types Foo and Bar cannot be compared with == or !=

Solution: If the == and != definitions are removed (from Any?) and implemented as extension methods, then the above code will run as expected.

Bonus feature: Once == and != are extension methods, then DSLs can finally use them to return types other than Boolean. For example, while it was always possible to do:

class MyBool
class Foo {
  def == (that : Int) : MyBool = ???
}

It did not allow to define commutative comparison:

val result1 : MyBool= new Foo == 1
val result2 : MyBool = 1 == newFoo //compile error

Implicit (extension) classes could not bypass the default ==, for the same reason extension methods currently can't.

drdozer commented 5 years ago

I like this approach. It would also allow us to remove equality from types by providing ambiguous implicits. For those situations where you absolutely require the axiom of choice to be a genuine choice.

odersky commented 5 years ago

It's a very intriguing thought, which might just work. Let's see:

the fallback can be used if one of the following is true:

These rules were developed because they give us good backwards compatibility and useful checking capabilities at the same time. It seems to me that these rules can be applied regardless of whether the fallbacks are members of Any or extension methods.

So, yes, definitely worth exploring further!

soronpo commented 5 years ago

I don't know much about binary compatibility and the extended methods backend. Dotty wants to share binaries with Scala 2. Is implementing a == as an extension instead of an internal class definition binary compatible?

drdozer commented 5 years ago

So there's two directions of interroperability here that I personally think are relevant. Using scala2 compiled code in scala3 source code, and using the scala3 compiled code in java source code. I would expect that interop for the scala2->scala3 case could be made automatic, by faking in == and != methods directly on those types when they are imported into the scala3 universe. Those methods would take prescedence over those supplied by extension methods.

The Java case is the one I'm not so sure of. That would require some boilerplate on the scala3 class files to bind .equals to the appropriate equality instance. Perhaps that's not a problem in reality, as java code won't very often need to rely upon scala's notion of equality.

soronpo commented 5 years ago

For a moment I thought I found an example of something that could break, if there is a class that overrides def == (that : Any), but fortunately this definition is final.

soronpo commented 5 years ago

FWIW, another benefit is that an IDE like IntelliJ error highlighting works better with missing extensions. So if we remove == from Any then two classes that cannot equal will get a red-squigly under ==.

Blaisorblade commented 5 years ago

@soronpo Been pointed to this issue by @AleksanderBG about #5810. I wonder if this would make a difference? But I suspect not, because that depends on rules for fallback (that could stay unchanged).

abgruszecki commented 5 years ago

If I'm reading the issue correctly, the current proposal is to:

  1. Permit any == defined as extension method w/o checking for Eq
  2. Define == as an extension method on Any
  3. Make that definition unavailable under language.strictEquality

If the above is correct, then #5810 is actually relevant here - we wanted to be able to accept code like:

 def f[T](x: T) = 
      if (x == null) ... 
      else if (x == "abc") ...
      else ...

which we would not be able to if == was unavailable on Any.

soronpo commented 5 years ago

BTW, is the plan for strictEquality to be opt-out instead of opt-in in the future?

abgruszecki commented 5 years ago

@soronpo Yes - it's a language import (in the scala.language sense).

Blaisorblade commented 5 years ago

@AleksanderBG I think we need something more complex than you describe, whatever we choose for #5810. We'd have to figure rules equivalent to today (with/without #5810).

Here's what happens with the rules @AleksanderBG describes: Without == on Any under strictEquality, we'd reject that code and regress without noticing, since we lack that test under git grep -l strictEquality tests. That issue asks to intentionally break that code under strictEquality, and require (x: Any) == "abc" (which is supported today). But if we drop the Any altogether, (x: Any) == "abc" would break too.

BTW, is the plan for strictEquality to be opt-out instead of opt-in in the future?

@soronpo Yes - it's a language import (in the scala.language sense).

@AleksanderBG Not sure that's the question — language imports are opt-in. FWIW, @OlivierBlanvillain proposed looseEquality time ago but that hasn't happened yet. Conversely, https://github.com/lampepfl/dotty/issues/1247#issuecomment-218508986 says it's not clear how important it'd be to move to strictEquality.

abgruszecki commented 5 years ago

Right, I read @soronpo's message the other way around. As far as I am aware, there are no plans to change the current approach to "loose" (permissive?) equality being the default.

soronpo commented 4 years ago

How do we advance on this? Is there time to make it available on 3.0? I can try to implement the simple stuff (remove the public == and != from the class and create the extension methods), but I have no idea where to implement the "magic". I would love it if typeclasses will be able to use == and != instead of === and =!=.

sighoya commented 4 years ago

I think old code with == and != needs to be rewritten to some part because the new multiversal equality is more constrained than equals?

odersky commented 4 years ago

I remember having looked at it before and then noticing that simply going to extension methods would not allow abstraction. E.g., you need a type class to define a safe version of contains. Extension methods alone don't help you there.

It might still be that extension methods would be a win, but our design space for 3.0 is very constrained by now:

That said, I would find it interesting to see the results of a large scale exploration what things would look like with extension methods. It will probably end up orthogonal to the question of constraining equality with a type class. Or maybe it will turn out that it's the type class that will contain the extension methods. It's interesting but also scary since equality is so pervasive and everything has to continue to work like it does now.