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

Extension method call fails because of wrong implicit resolution #12713

Closed
eugeneatnezasa opened this issue Jun 4, 2021 · 4 comments
Closed

Comments

@eugeneatnezasa
Copy link

Compiler version

3.0.0, 3.0.1-RC1

Minimized code

extension [T: Fractional](base: List[T])
  def average: T = summon[Fractional[T]].div(base.sum, summon[Fractional[T]].fromInt(base.size))

extension [T: Integral](base: List[T])
  def average: T = summon[Integral[T]].quot(base.sum, summon[Integral[T]].fromInt(base.size))

val l1 = List(1,2,3)
val l2 = List(1.0,2.0,3.0)

/*ok*/
l1.average

/* fails */
l2.average

Output

1 |l2.average
  |^^^^^^^^^^
  |value average is not a member of List[Double].
  |An extension method was tried, but could not be fully constructed:
  |
  |    average[Double](l2)(/* missing */summon[Integral[Double]])    failed with
  |
  |        No implicit Ordering defined for Double.

Expectation

Expected that List[Double] will be extended using [T: Fractional] extension method

@odersky
Copy link
Contributor

odersky commented Jun 5, 2021

As far as I can see both l1.average and l2.average fail. I get:

11 |  val _ = l1.average
   |          ^^^^^^^^^^
   |value average is not a member of List[Int].
   |An extension method was tried, but could not be fully constructed:
   |
   |    average(l1)    failed with
   |
   |        Ambiguous overload. The overloaded alternatives of method average with types
   |         [T](base: List[T])(implicit evidence$2: Integral[T]): T
   |         [T](base: List[T])(implicit evidence$1: Fractional[T]): T
   |        both match arguments ((l1 : List[Int]))
-- [E008] Not Found Error: test.scala:14:13 ------------------------------------
14 |  val _ = l2.average
   |          ^^^^^^^^^^
   |value average is not a member of List[Double].
   |An extension method was tried, but could not be fully constructed:
   |
   |    average(l2)    failed with
   |
   |        Ambiguous overload. The overloaded alternatives of method average with types
   |         [T](base: List[T])(implicit evidence$2: Integral[T]): T
   |         [T](base: List[T])(implicit evidence$1: Fractional[T]): T
   |        both match arguments ((l2 : List[Double]))
2 errors found

And that's as expected. Overloading resolution does not take subsequent givens into account.

@odersky odersky closed this as completed Jun 5, 2021
@eugeneatnezasa
Copy link
Author

Thank you for your comment @odersky
This was a rewrite from scala2 code:

implicit class IntegralList[T: Integral](base: List[T]) {
  def average = implicitly[Integral[T]].quot(base.sum, implicitly[Integral[T]].fromInt(base.size))
}

implicit class FractionalList[T: Fractional](base: List[T]) {
  def average = implicitly[Fractional[T]].div(base.sum, implicitly[Fractional[T]].fromInt(base.size))
}

val l1 = List(1,2,3)
val l2 = List(1.0,2.0,3.0)

l1.average
l2.average

May I kindly ask you how this code can be overwritten in scala3 then? Thank you

@som-snytt
Copy link
Contributor

The difference in behavior is perhaps due to scala/scala#3030 where the explanatory comment is here, for ticket 3346.

The Scala 2 spec is clear that overloading picks among implicits, but under the current regime, it eliminates some implicit views by testing applications. The logging looks like:

[log typer] implicit adapt failed: could not find implicit value for evidence parameter of type Integral[scala.this.Double] (No implicit Ordering defined for scala.this.Double.)
t12713.scala:19: TI is not a valid implicit value for scala.this.Function1[Test.this.l2.type,?{<method> def average: ?}] because:
could not find implicit value for evidence parameter of type Integral[scala.this.Double] (No implicit Ordering defined for scala.this.Double.)
    (l1.average, l2.average)
                 ^

Sample counter-example which doesn't compile under Scala 3:

✗ scalac -d /tmp test/files/neg/t3346i.scala
-- [E007] Type Mismatch Error: test/files/neg/t3346i.scala:28:3 ------------------------------------------------------
28 |  (new A).a
   |   ^^^^^
   |   Found:    Test.A[Nothing]
   |   Required: ?{ a: ? }
   |   Note that implicit conversions cannot be applied because they are ambiguous;
   |   both method implicit3alt1 in object Test and method implicit3alt2 in object Test match type Implicit3[T]

Some of those tests are enabled in the dotty repo:

./tests/pending/run/t3346e.scala
./tests/disabled/reflect/run/t3346j.scala
./tests/disabled/reflect/run/t3346f.scala
./tests/run/t3346h.scala
./tests/run/t3346d.scala
./tests/run/t3346g.scala
./tests/run/t3346a.scala
./tests/untried/neg/t3346b.scala
./tests/untried/neg/t3346c.scala
./tests/untried/neg/t3346i.scala

@eugeneatnezasa
Copy link
Author

Thank you @som-snytt for explanations..

Finally I managed to rewrite it with Conversions:

  trait ListAvg[T]:
    def average: T

  class IntList[T: Integral](base: List[T]) extends ListAvg[T]:
    def average: T = summon[Integral[T]].quot(base.sum, summon[Integral[T]].fromInt(base.size))

  class FraList[T: Fractional](base: List[T]):
    def average: T = summon[Fractional[T]].div(base.sum, summon[Fractional[T]].fromInt(base.size))

  given [T](using Integral[T]): Conversion[List[T], IntList[T]] = new Conversion[List[T], IntList[T]] {
    def apply(base: List[T]): IntList[T] = new IntList(base)
  }
  given [T](using Fractional[T]): Conversion[List[T], FraList[T]] = new Conversion[List[T], FraList[T]] {
    def apply(base: List[T]): FraList[T] = new FraList(base)
  }

  val l1 = List(1,2,3)
  val l2 = List(1.0,2.0,3.0)

  l1.average
  l2.average

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

No branches or pull requests

3 participants