Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: implement multiversal equality using extension methods #52

Open
soronpo opened this issue Jan 27, 2019 · 15 comments
Open

Proposal: implement multiversal equality using extension methods #52

soronpo opened this issue Jan 27, 2019 · 15 comments
Assignees

Comments

@soronpo
Copy link

@soronpo soronpo commented Jan 27, 2019

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

This comment has been minimized.

Copy link

@drdozer drdozer commented Jan 27, 2019

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

This comment has been minimized.

Copy link

@odersky odersky commented Jan 28, 2019

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

  • Would things break if == and != were extension methods instead of methods of Any? I don't see anything at the moment. That's something we should be able to test quickly in isolation of the rest of the proposal.

  • The two fallback extension methods would not be available under -language.strictEquality. That can be arranged.

  • If a type defines its own extension methods != and == these will win against the fallback since they have more specific types.

  • The only additional magic we'd need is for the situation where we do not have strictEquality set. Under multiversal equality, there's a subtle set of rules that determines whether we can use the fallback eqAny instance. In

    (x: S) == (y: T)

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

  • S <: T or T <: S
  • neither S nor T has a == extension method (or rather, implements the equality class)

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

This comment has been minimized.

Copy link
Author

@soronpo soronpo commented Jan 28, 2019

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

This comment has been minimized.

Copy link

@drdozer drdozer commented Jan 28, 2019

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

This comment has been minimized.

Copy link
Author

@soronpo soronpo commented Jan 28, 2019

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

This comment has been minimized.

Copy link
Author

@soronpo soronpo commented Jan 28, 2019

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

This comment has been minimized.

Copy link

@Blaisorblade Blaisorblade commented Jan 28, 2019

@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).

@AleksanderBG

This comment has been minimized.

Copy link

@AleksanderBG AleksanderBG commented Jan 28, 2019

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

This comment has been minimized.

Copy link
Author

@soronpo soronpo commented Jan 28, 2019

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

@AleksanderBG

This comment has been minimized.

Copy link

@AleksanderBG AleksanderBG commented Jan 28, 2019

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

@Blaisorblade

This comment has been minimized.

Copy link

@Blaisorblade Blaisorblade commented Jan 29, 2019

@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, lampepfl/dotty#1247 (comment) says it's not clear how important it'd be to move to strictEquality.

@AleksanderBG

This comment has been minimized.

Copy link

@AleksanderBG AleksanderBG commented Jan 29, 2019

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.

@liufengyun liufengyun transferred this issue from lampepfl/dotty Jul 19, 2019
@soronpo

This comment has been minimized.

Copy link
Author

@soronpo soronpo commented Nov 28, 2019

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

This comment has been minimized.

Copy link

@sighoya sighoya commented Dec 15, 2019

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

@odersky

This comment has been minimized.

Copy link

@odersky odersky commented Dec 16, 2019

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:

  • We want to keep going with the 2.x standard library
  • We are basically out of time

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
6 participants
You can’t perform that action at this time.