Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.
like image 263
VoidBzY Avatar asked Aug 16 '16 04:08

VoidBzY


2 Answers

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...

like image 182
Alec Avatar answered Nov 01 '22 12:11

Alec


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
}

enter image description here

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.

like image 42
Yaneeve Avatar answered Nov 01 '22 11:11

Yaneeve