Search code examples
scalaimplicit

Why is no implicit view found, when eta conversion and specifying type parameters does allow an implicit view to be found?


The code:

object Test {
  import scala.language.implicitConversions

  case class C1() {}
  case class C2() {}

  implicit def c1ToC2(in: C1): C2 = C2()

  def from[A, B](in: A)(implicit f: A => B): B = f(in)

  def fails(): Future[C2] = {
    val future: Future[C1] = Future.successful(C1())
    future.map(from) // this line fails to compile!
  }

  def compiles1(): Future[C2] = {
    val future: Future[C1] = Future.successful(C1())
    future.map(x => from(x))
  }

  def compiles2(): Future[C2] = {
    val future: Future[C1] = Future.successful(C1())
    future.map(from[C1, C2])
  }
}

In this example, only the fails method fails to compile. The error message is:

Error:(23, 16) No implicit view available from A => B. future.map(from)

I'm confused about why no implicit view is found. Based on the compiles1 and compiles2 methods, which both successfully compile, it seems there is an implicit view available from A => B.

What is going on here, and why do the two compilesN methods work, but fails does not?

My background: I'm still learning Scala, so it could easily be the case that I'm missing something pretty obvious. :)

I'm on Scala 2.11.8.


Solution

  • The compiler attempts to resolve implicits before eta-expansion of from into a function, so the type parameters of from are not yet inferred when you call it like this:

    future.map(from)
    

    compiles2 obviously works because you supply the type parameters on your own. When you call future.map(from[C1, C2]), the compiler knows it will need an implicit C1 => C2, because that's what you've told it.

    With compiles1, the difference is a little more subtle, but it stems from the fact that future.map(from) and future.map(x => from(x)) are actually very different things. The former uses eta-expansion, which fails for the aforementioned reasons. With future.map(x => from(x)), there is no eta-expansion happening. Instead, you have an anonymous function that simply calls from instead of eta-expanding it. Therefore, the compiler can infer the type of x, which tells us that x is a C1 (in this case), and it can find the implicit conversion c1ToC2 that satisfies the type parameters of from while resolving the implicit and the final return type of the method, Future[C2].