Skip to content

Derived type class instances do not seem to be eligible for extension methods #5773

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
LPTK opened this issue Jan 22, 2019 · 3 comments · Fixed by #11274
Closed

Derived type class instances do not seem to be eligible for extension methods #5773

LPTK opened this issue Jan 22, 2019 · 3 comments · Fixed by #11274
Milestone

Comments

@LPTK
Copy link
Contributor

LPTK commented Jan 22, 2019

See:

trait Semigroup[T] {
  def (x: T) combine (y: T): T
}
implicit val IntSemigroup: Semigroup[Int] = new {
   def (x: Int) combine (y: Int): Int = x + y
}
implicit def OptionSemigroup[T: Semigroup]: Semigroup[Option[T]] = new {
   def (x: Option[T]) combine (y: Option[T]): Option[T] = for {
     x0 <- x
     y0 <- y
   } yield x0.combine(y0)
}

This works:

scala> 1.combine(2)
val res0: Int = 3

But not this:

scala> Some(1).combine(Some(2))
1 |Some(1).combine(Some(2))
  |^^^^^^^^^^^^^^^
  |value combine is not a member of Some[Int]

not this:

scala> Option(1) combine Option(2)
1 |Option(1) combine Option(2)
  |^^^^^^^^^^^^^^^^^
  |value combine is not a member of Option[Int]

But this does:

scala> implicitly[Semigroup[Option[Int]]].combine(Some(1))(Some(2))
val res1: Option[Int] = Some(3)
@guyde2011
Copy link

This works as well:

val optSG = implicitly[Semigroup[Option[Int]]]
val some5 = {
  implicit val impSG = optSG
  Some(2) combine Some(3)
}

Implicit classes seem to not have this problem:

implicit class RichSemigrouped[SG: Semigroup](x: SG){
  def combine2(y: SG) = x combine y
}
val some5 = Some(3) combine2 Some(2) //still doesn't work
val optionInt: Option[Int] = Some(3)
val x1 = optionInt combine Some(2) // doesn't work
val x2 = optionInt combine2 Some(2) // works

@drdozer
Copy link

drdozer commented Jan 23, 2019

trait Semigroup[T] {
  def (lhs: T) append (rhs: T): T
}

object Semigroup {
  implicit object stringAppend extends Semigroup[String] {
    override def (lhs: String) append (rhs: String): String = lhs + rhs
  }

  implicit def sumSemigroup[N](implicit N: Numeric[N]): Semigroup[N] = new {
    override def (lhs: N) append (rhs: N): N = N.plus(lhs, rhs)
  }
}


object Main {
  def main(args: Array[String]): Unit = {
    import Semigroup.stringAppend // necessary to make the extension method visible
    println("Hi" append " mum")

    import Semigroup.sumSemigroup // this is not sufficient
    println(1 append 2) // this won't compile

    implicit val intSumAppend: Semigroup[Int] = sumSemigroup[Int]
    println(3 append 4) // this will
  }
}

@odersky
Copy link
Contributor

odersky commented Feb 3, 2019

This is a tough one. The problem is that, relative to implicit classes search order for the Numeric implicit is reversed.
Picking @drdozer's example:

1 append 2

will expand to

Semigroup.sumSemigroup[N](implicit N: Numeric[N])

At that point we are forced to do an implicit search which will fail with an ambiguous result, since N is not yet specified. #5836 improves the diagnostics, so this is visible now. You now get:

-- [E008] Member Not Found Error: i5773.scala:25:14 ----------------------------
25 |    println(1 append 2) // this won't compile
   |            ^^^^^^^^
   |value append is not a member of Int.
   |An extension method was tried, but could not be fully constructed:
   |
   |    Semigroup.sumSemigroup[Any](/* ambiguous */implicitly[Numeric[Any]]).append()

A possible fix would be to move the implicit from the sumSemigroup to the append method. Then it gets elaborated after seeing the first operand 1, so N is instantiated to Int before the implicit search is performed. That would have the added advantage that it's more efficient. But it does restrict what can be expressed.

@odersky odersky closed this as completed in 8726cf7 Feb 7, 2019
odersky added a commit to dotty-staging/dotty that referenced this issue Feb 1, 2021
When retrying after an ambiguous implicit, we now make use of all the
information in the prototype, including ignored parts. We also try
to match formal parameters with actually given arguments.

Fixes scala#11243
Fixes scala#5773, which previously was closed with a more detailed error message.
odersky added a commit to dotty-staging/dotty that referenced this issue Feb 1, 2021
When retrying after an ambiguous implicit, we now make use of all the
information in the prototype, including ignored parts. We also try
to match formal parameters with actually given arguments.

Fixes scala#11243
Fixes scala#5773, which previously was closed with a more detailed error message.
odersky added a commit to dotty-staging/dotty that referenced this issue Feb 1, 2021
When retrying after an ambiguous implicit, we now make use of all the
information in the prototype, including ignored parts. We also try
to match formal parameters with actually given arguments.

Fixes scala#11243
Fixes scala#5773, which previously was closed with a more detailed error message.
michelou pushed a commit to michelou/scala3 that referenced this issue Feb 5, 2021
When retrying after an ambiguous implicit, we now make use of all the
information in the prototype, including ignored parts. We also try
to match formal parameters with actually given arguments.

Fixes scala#11243
Fixes scala#5773, which previously was closed with a more detailed error message.
@Kordyjan Kordyjan added this to the 3.0.0 milestone Aug 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants