10

In scala, the following code compiles properly:

class a {}
class b {}

object Main {

  implicit class Conv[f, t](val v: f ⇒ t) extends AnyVal {
    def conv = v
  }

  def main(args: Array[String]) {
    val m = (a: a) ⇒ new b
    m.conv
  }
}

But for some reason the following fails to compile:

class a {}
class b {}

object Main {
  type V[f, t] = f ⇒ t

  implicit class Conv[f, t](val v: V[f, t]) extends AnyVal {
    def conv = v
  }

  def main(args: Array[String]) {
    val m = (a: a) ⇒ new b
    m.conv
  }
}

with the following message:

value conv is not a member of a => b
    m.conv

Why does this happen?

EDIT: Yes, there is still an error even with

  val m: V[a,b] = new V[a,b] { def apply(a: a) = new b }
Prikso NAI
  • 2,592
  • 4
  • 16
  • 29
  • 3
    Well, you can fix this by declaring the variance of the function parameter in the type alias: `type V[-f, t] = f ⇒ t` – Kolmar Aug 09 '15 at 15:52
  • well you do not have an implicit conversion from `(a => b)` to `Conv[a,b]` so I first thought annotating the type should help `val m: V[a, b] = (a: a) ⇒ new b`. But as it turns out, that still does not compile changing the error to `value conv is not a member of V[a,b] | m.conv` (though IntelliJ Idea finds the implicit conversion). – Sascha Kolberg Aug 09 '15 at 15:53

1 Answers1

4

In your first example, val v: f => t is inferred to a type signature [-A, +B], because it is shorthand for a Function of one parameter. Function1 has the type signature, Function1[-A, +B]. So, a type which is Contravariant in the A parameter and Covariant in the B parameter.

Then the lambda function, (a: a) => new b later in the code, has it's type inferred as a function from a to b. So, the type signature is identical and implicit resolution works.

In your second example type V[f, t] = f => t and the parameter created from it: val v: V[f, t], have their type explicitly specified as V[f, t]. The function f => t would still be [-A, +B], but you explicitly restrict your types to being Invariant in the type signature, so the type V is Invariant in both type parameters.

Later, when you declare: val m = (a: a) => new b the type signature of this would still be [-A, +B] as in the first example. Implicit resolution fails to work because the val is Contravariant in its first type parameter but the type V is Invariant in its first type parameter.

Changing the type signature of V to either V[-f, +t] or V[-f, t] resolves this and implicit resolution works once again.

This does raise a question about why the Covariance of the second type parameter is not a problem for implicit resolution, whilst the Contravariance of the first type parameter is. I played around and did a little bit of research. I came across a few interesting links, which indicates that there are definitely some limitations/issues around implicit resolution specifically when it comes to Contravariance.

I would have to defer to someone with knowledge of the internals of the Scala compiler and the resolution mechanics for more detail, but it seems that this is running afoul of some limitations around implicit resolution in the context of Contravariance.

For your third example, I think you mean:

class a {}
class b {}

object Main {
  type V[f, t] = f => t

  implicit class Conv[f, t](val v: V[f, t]) extends AnyVal {
    def conv = v
  }

  def main(args: Array[String]) {
    val m: V[a,b] = new V[a,b] { def apply(a: a) = new b }
    m.conv // does not compile
  }
}

This is an interesting one and I think it is a slightly different cause. Type Aliases are restricted in what they can change when it comes to variance, but making the variance stricter is allowed. I can't say for sure what is happening here, but here is an interesting Stack Overflow question related to type aliases and variance.

Given the complexity of implicit resolution, combined with the additional factors of the type declaration variance versus the implied Function1 variance I suspect the compiler is just not able to resolve anything in that specific scenario.

Changing to:

implicit class Conv(val v: V[_, _]) extends AnyVal {
  def conv = v
}

means that it works in all scenarios, because you are basically saying to the compiler, that for the purposes of the Conv implicit class, you don't care about the variance of the type parameters on V.

e.g: the following also works

class a {}
class b {}

object Main {
  type V[f, t] = f ⇒ t

  implicit class Conv(val v: V[_, _]) extends AnyVal {
    def conv = v
  }

  def main(args: Array[String]) {
    val m = (a: a) ⇒ new b
    m.conv
  }
}
Community
  • 1
  • 1
Mike Curry
  • 1,609
  • 1
  • 9
  • 12
  • Thank you, this is a great answer, I have been suspecting it has something to do with variance. All these facts do not answer the comment of my edit, namely why explicitly creating a V[f,t] object (`val m: V[a,b] = new V[a,b] { def apply(a: a) = new b }`) still fails to be implicitly converted to `Conv`. I suspect this is a bug and has no real answer, but just in case I will wait a little bit more about it. Or am I missing something? – Prikso NAI Aug 11 '15 at 15:01