7
val d: Double = 42

When I try to find implicit conversion via intellij, nothing interesting comes up. Also, Int isn't a subtype of Double. So how does Scala do it?

allidoiswin
  • 2,543
  • 1
  • 20
  • 23
  • Possible duplicate of [How do I do casting in Scala?](https://stackoverflow.com/questions/6714967/how-do-i-do-casting-in-scala) – Stoopkid Mar 08 '18 at 00:04
  • @Stoopkid In a sense, my question is the opposite of that thread: I'm almost asking why I *don't* need a `.toDouble` – allidoiswin Mar 08 '18 at 00:16

1 Answers1

12

Long story short: it's not an ordinary implicit conversion on some companion object, the numerical types get a special treatment.


If we run scala -print on this script:

val d: Double = 42

we obtain:

package <empty> {
  object Main extends Object {
    def main(args: Array[String]): Unit = {
      new <$anon: Object>();
      ()
    };
    def <init>(): Main.type = {
      Main.super.<init>();
      ()
    }
  };
  final class anon$1 extends Object {
    private[this] val d: Double = _;
    <stable> <accessor> private def d(): Double = anon$1.this.d;
    def <init>(): <$anon: Object> = {
      anon$1.super.<init>();
      anon$1.this.d = 42.0;
      ()
    }
  }
}

In the desugared code, we see a double literal 42.0, but no invocations of any conversion functions (e.g. from Predef). Thus, the conversion from Int to Double must take place not at runtime, but at earlier stages of compilation.

The section 3.5.3 of the specification tells us that Int weakly conforms to Double because of the transitivity of the weak conformance relation <:w:

Int <:w Long <:w Float <:w Double

Furthermore, Section 6.26.1 (Value Conversions) tells us that rules for numeric widening are applicable if an expression e of type T appears in position where an expression of type pt is expected and T weakly conforms to pt. In this case, we can apply the rule with

  • expression e = 42
  • type of expression T = Int
  • expected type pt = Double

Thus, 42 is converted to 42.0 using toDouble. Since it's a constant that can be processed at compile time, we don't see the toDouble in the desugared code. However, if we desugar a similar program with a non-constant value

val d: Double = (new scala.util.Random).nextInt(42)

we obtain:

package <empty> {
  object Main extends Object {
    def main(args: Array[String]): Unit = {
      new <$anon: Object>();
      ()
    };
    def <init>(): Main.type = {
      Main.super.<init>();
      ()
    }
  };
  final class anon$1 extends Object {
    private[this] val d: Double = _;
    <stable> <accessor> private def d(): Double = anon$1.this.d;
    def <init>(): <$anon: Object> = {
      anon$1.super.<init>();
      anon$1.this.d = new scala.util.Random().nextInt(42).toDouble();
      ()
    }
  }
}

and the toDouble is there, as specified.

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
  • Very informative, especially re: the weak conformance details. My assumption of the Int-to-Double implicit conversion being at play under the hood is incorrect then. – Leo C Mar 08 '18 at 01:05
  • @LeoC It's a plausible assumption, though. I can't spontaneously invent an example where one would actually notice the difference without looking at the desugared code. – Andrey Tyukin Mar 08 '18 at 01:12