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

2

12

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.
Sadonia answered 16/8, 2016 at 4:20 Comment(1)
Duplicate #28252560Severable
T
15

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

Thriller answered 16/8, 2016 at 5:13 Comment(0)
F
3

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.

Fuqua answered 16/8, 2016 at 5:8 Comment(9)
Use -Ywarn-numeric-widen to make the conversions visible.Severable
Misleadingly, the implicit has, in this case, nothing to do with the issue.Thriller
@Fuqua Alecs answer explains it well.Gordon
@Thriller the ML thread you quote includes example of the companion implicit methods used as fallback mechanism in 2.10, and for views in 2.11.Severable
@Severable I don't think that comment was meant for me. :)Thriller
@Thriller for both of you, to clarify the status of the methods vis-a-vis numeric widening. Another footnote is that spec says the conversions are defined in Predef, which is not (now) true.Severable
@som-snytt, thanks so much for your input. Now, though, I am somewhat lost. I don't know what "ML thread" is, and I can't even guess (sorry for the ignorance). Also, please, elaborate on what it means "example of the companion implicit methods used as fallback mechanism in 2.10, and for views in 2.11."Fuqua
@Fuqua pardon, the other answer has "old link" for a Mailing List thread, which is still classic. See the Paul Phillips message for example that uses the conversion method in 2.10 but not in 2.11; Jason Zaugg chimes in that the methods are still available in 2.11 if you want Int => Double etc.Severable
Thanks for the (double) clarification :)Fuqua

© 2022 - 2024 — McMap. All rights reserved.