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 }
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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With