Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Overload breaks implicit resolution #17794

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

Closed
Adam-Vandervorst opened this issue Dec 18, 2021 · 9 comments
Closed

Overload breaks implicit resolution #17794

Adam-Vandervorst opened this issue Dec 18, 2021 · 9 comments

Comments

@Adam-Vandervorst
Copy link

Compiler version

3.1.2-RC1-bin-20211205-94f4118-NIGHTLY

Minimized code

class A
class B

def f(using a: A): Int = 42
def f(using b: B): Int = 41

@main def m14 =
  given A = new A
  assert(f == 42)

Output

  assert(f == 42)
         ^
Ambiguous overload. The overloaded alternatives of method f in package overload_issue with types
 (using b: overload_issue.B): Int
 (using a: overload_issue.A): Int
both match expected type ?{ == : ? }

Expectation

Compile or give a less cryptic expected type than ?{ == : ? }.
This works fine without this line: def f(using b: B): Int = 41.

@som-snytt
Copy link
Contributor

That's just your overloading dollars at work.

Workaround is to turn it into application explicitly

assert(f(using summon[A]) == 42)

It would be interesting if that could be abbreviated f(using). Then overloading could pick the alternative that applies.

Scala 2 expects less and says

match expected type ?

with its voice rising at the end like an English question.

@odersky
Copy link
Contributor

odersky commented Dec 19, 2021

The behavior is as expected, so the only open question is about the error message.

Is the expected type really that hard to decypher? It says: anything that has a == method that
has an arbitrary type. We could try to put that in more words, but maybe the meaning would get more diluted? Doing this would require considerable effort so I want everyone convinced that it is really worth it before someone dives into this.

@Adam-Vandervorst
Copy link
Author

You both alluded to this is expected behavior.
Yet I don't see why making these parameters implicit (one way to look at it), or adding an overload with a different argument type (another way to look at it), would not work.

The second question that pops up with both match expected type is: why is it looking at the expected result type instead of the provided parameters?
Something like def f(a: A): Int = 42; def f(a: A): Boolean = false having different result types does not even make sense for an overload.

The third question is: Isn't given A = new A; assert(f == 42) supposed to expand to val a = new A; assert(f(using a) == 42)?

The error message has an unfortunate thing going on; the method name is symbolic.
Putting that name between backticks may improve it somewhat, or prefixing it with def to show it's a method.
It doesn't help the colon has a leading space, whereas in the signature right above it, doesn't.

@odersky
Copy link
Contributor

odersky commented Dec 19, 2021

It's to prevent combinatorial explosion. When choosing between overloaded alternatives we assume that any following implicit searches would succeed. If we did not do that, some programs would get spectacularly slow compile times.

@Adam-Vandervorst
Copy link
Author

Interesting! I'm stumbling on a lot of overloading issues (#13768, #7792, #12663).

I assume there is no compiler flag for this or an opt-in for certain functions?

Could a decorator expand the example to the following (which works)?

def f_1(using A): Int = 42
def f_2(using B): Int = 41
inline def f_overload(using a_b: A | B): Int =
  inline a_b match
    case given A => f_1
    case given B => f_2

@main def m14 =
  given A = new A
  println(f_overload)

@som-snytt
Copy link
Contributor

Or just issue a warning if overload resolution incurs implicit search.

@smarter smarter transferred this issue from scala/scala3 Dec 20, 2021
@LPTK
Copy link
Contributor

LPTK commented Dec 21, 2021

If you're going to use implicit parameters, you may as well go full on and use the magnet pattern instead of normal overloading:

class A
class B

given left [A, B](using A): Either[A, B] = Left (summon)
given right[A, B](using B): Either[A, B] = Right(summon)

def f(using ab: Either[A, B]): Int = ab.fold(_ => 42, _ => 41)

@main def m14 =
  given A = new A
  assert(f == 42)

def blah =
  given B = new B
  assert(f == 41)

https://scastie.scala-lang.org/CQO7a3G0SdmAhWmYMYJWhg

@Adam-Vandervorst
Copy link
Author

In some way that's similar to my expansion, but not as powerful @LPTK

  • it doesn't generalize to an arbitrary number of variables
  • variables of different "overloaded functions" their given's can collide
  • you have the boilerplate of the left and right givens

In my original problem I have over 30 functions like f with each 1, 2, or 3 (overloaded) alternatives.

@LPTK
Copy link
Contributor

LPTK commented Dec 22, 2021

In some way that's similar to my expansion

Ah yes I'd missed what you proposed above. Yes, both are basically what is known as the "magnet pattern".

  • it doesn't generalize to an arbitrary number of variables
  • variables of different "overloaded functions" their given's can collide

Of course you don't have to use Either; this was just an example... The most general way of doing this is to introduce one new type per parameter of the overloaded function.

In my original problem I have over 30 functions like f with each 1, 2, or 3 (overloaded) alternatives.

Then all you need are three types Param1[T], Param2[T] and Param3[T] with corresponding given instances, and you can use your type union idea together with it, eg: def f(using p1: Param1[A | B], p2: Param2[B | D | E]).

Note that you can play on implicit prioritization and specificity to resolve things how you want them to be. The magnet pattern is fully type-based so it's the more powerful form of overloading available in Scala.

@ckipp01 ckipp01 transferred this issue from lampepfl/dotty-feature-requests Jun 5, 2023
@scala scala locked and limited conversation to collaborators Jun 5, 2023
@ckipp01 ckipp01 converted this issue into discussion #17795 Jun 5, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants