I am trying to understand why exactly an implicit conversion is working in one case, but not in the other. Here is an example:
case class Wrapper[T](wrapped: T)
trait Wrapping { implicit def wrapIt[T](x: Option[T]) = x.map(Wrapper(_))
class NotWorking extends Wrapping { def foo: Option[Wrapper[String]] = Some("foo") }
class Working extends Wrapping {
def foo: Option[Wrapper[String]] = {
val why = Some("foo")
why
}
}
Basically, I have an implicit conversion from Option[T]
to Option[Wrapper[T]]
, and am trying to define a function, that returns an optional string, that gets implicitly wrapped.
The question is why, when I try to return Option[String]
directly (NotWorking
above), I get an error (found : String("foo") required: Wrapper[String]
), that goes away if I assign the result to a val before returning it.
What gives?
Implicit conversions are evil, for several reasons. They make it hard to see what goes on in code. For instance, they might hide bad surprises like side effects or complex computations without any trace in the source code.
In implicit typecasting, the conversion involves a smaller data type to the larger type size. For example, the byte datatype implicitly typecast into short, char, int, long, float, and double. The process of converting the lower data type to that of a higher data type is referred to as Widening.
Keyword explicit tells compiler to not use the constructor for implicit conversion. For example declaring Bar's constructor explicit as - explicit Bar(int i); - would prevent us from calling ProcessBar as - ProcessBar(10); .
An implicit conversion from type S to type T is defined by an implicit value which has function type S => T , or by an implicit method convertible to a value of that type. Implicit conversions are applied in two situations: If an expression e is of type S , and S does not conform to the expression's expected type T .
I don't know if this is intended or would be considered a bug, but here is what I think is happening.
In def foo: Option[Wrapper[String]] = Some("foo")
the compiler will set the expected type of the argument provided to Some( )
as Wrapper[String]
. Then it sees that you provided a String
which it is not what is expected, so it looks for an implicit conversion String => Wrapper[String]
, can't find one, and fails.
Why does it need that expected type stuff, and doesn't just type Some("foo")
as Some[String]
and afterwards try to find a conversion?
Because scalac wants to be able to typecheck the following code:
case class Invariant[T](t: T)
val a: Invariant[Any] = Invariant("s")
In order for this code to work, the compiler can't just type Invariant("s")
as Invariant[String]
because then compilation will fail as Invariant[String]
is not a subtype of Invariant[Any]
. The compiler needs to set the expected type of "s"
to Any
so that it can see that "s"
is an instance of Any
before it's too late.
In order for both this code and your code to work out correctly, I think the compiler would need some kind of backtracking logic which it doesn't seem to have, perhaps for good reasons.
The reason that your Working
code does work, is that this kind of type inference does not span multiple lines. Analogously val a: Invariant[Any] = {val why = Invariant("s"); why}
does not compile.
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