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
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 methodstoShort
,toChar
,toInt
,toLong
,toFloat
,toDouble
...
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...
Look at the last line in the Int companion object. I think it has to do with that and the concept of views:
object Int extends AnyValCompanion {
/** The smallest value representable as a Int.
*/
final val MinValue = java.lang.Integer.MIN_VALUE
/** The largest value representable as a Int.
*/
final val MaxValue = java.lang.Integer.MAX_VALUE
/** Transform a value type into a boxed reference type.
*
* @param x the Int to be boxed
* @return a java.lang.Integer offering `x` as its underlying value.
*/
def box(x: Int): java.lang.Integer = java.lang.Integer.valueOf(x)
/** Transform a boxed type into a value type. Note that this
* method is not typesafe: it accepts any Object, but will throw
* an exception if the argument is not a java.lang.Integer.
*
* @param x the java.lang.Integer to be unboxed.
* @throws ClassCastException if the argument is not a java.lang.Integer
* @return the Int resulting from calling intValue() on `x`
*/
def unbox(x: java.lang.Object): Int = x.asInstanceOf[java.lang.Integer].intValue()
/** The String representation of the scala.Int companion object.
*/
override def toString = "object scala.Int"
/** Language mandated coercions from Int to "wider" types.
*/
implicit def int2long(x: Int): Long = x.toLong
implicit def int2float(x: Int): Float = x.toFloat
implicit def int2double(x: Int): Double = x.toDouble
}
See also: Where does Scala look for implicits?
EDIT
as per @som-snytt's comment:
scala -Ywarn-numeric-widen
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_91).
Type in expressions for evaluation. Or try :help.
scala> 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
| }
<console>:22: warning: implicit numeric widening
val y: Double = x
^
Also, to add to @Alec's answer about widening: http://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html#value-conversions
from: http://www.scala-lang.org/files/archive/spec/2.11/03-types.html#weak-conformance
Weak Conformance In some situations Scala uses a more general conformance relation. A type >SS weakly conforms to a type TT, written S<:wTS<:wT, if S<:TS<:T or both >SS and TT are primitive number types and SS precedes TT in the following >ordering.
Byte <:w<:w Short
Short <:w<:w Int
Char <:w<:w Int
Int <:w<:w Long
Long <:w<:w Float
Float <:w<:w Double
A weak least upper bound is a least upper bound with respect to weak conformance.
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