VoidBzY VoidBzY - 3 months ago 35
Scala Question

Implicit conversion from Int to Double in scala doesn't work

I have written some implicit code as shown below, and I am wondering why the

i2d
function implicit conversation isn't invoked.

object Test {
implicit def i2d(x: Int): Double = {
println("foo")
x.toDouble
}

implicit def d2i(x: Double): Int = {
x.toInt
}

val x: Int = 10
val y: Double = x
val z: Int = 3.5
}


The output of
scalac -Xprint:typer Test.scala


// Test.scala
[[syntax trees at end of typer]]
package <empty> {
object Test extends scala.AnyRef {
def <init>(): Test.type = {
Test.super.<init>();
()
};
implicit def i2d(x: Int): Double = {
scala.this.Predef.println("foo");
x.toDouble
};
implicit def d2i(x: Double): Int = x.toInt;
private[this] val x: Int = 10;
<stable> <accessor> def x: Int = Test.this.x;
private[this] val y: Double = Test.this.x.toDouble;
<stable> <accessor> def y: Double = Test.this.y;
private[this] val z: Int = Test.this.d2i(3.5);
<stable> <accessor> def z: Int = Test.this.z
}
}


Specs



  • scalac version is 2.11.8.


Answer

This was a lot more involved than I thought.

At first, I thought it was because of how Scala resolves implicits. Somehow, I thought this implicit in Int.scala was getting prioritized. The rules for priority are usually intuitive, but for edge cases like this, we refer to 6.26.3 Overloading Resolution. The thing that confused me though was that the call to int2double isn't there - it is already inlined to .toDouble!!

Having dug a bit more, it appears that there is an edge case concerning value types which applies to the conversion from Int to Double. Something called weak conformance dictates how we convert Byte -> Short -> Int -> Long -> Float -> Double. So, all in all, I don't think you can overrule this builtin conversion...

This conversion is known as numeric widening

Numeric Widening

If e has a primitive number type which weakly conforms to the expected type, it is widened to the expected type using one of the numeric conversion methods toShort, toChar, toInt, toLong, toFloat, toDouble...

EDIT

Also, in case anyone is wondering why this is a thing, from Martin Odersky (this is an old link - don't trust what is being said here in general), we run into common problems if we don't have these extra special conversions:

scala-hypothetical> val x = List(1, 2.0) 
x: List[AnyVal]

Not very intuitive...