Scala implicit conversions

Scala is a statically typed language, meaning that the Scala compiler will know types of every variable we use in our programs at compile time. To play well with the compiler we will have to specify each variable’s type.

This can feel as a tedious work and less flexible than dynamic languages such as PHP, Ruby or Python. But Scala and its compiler have features that ease things and make you enjoy the best of the 2 worlds.

The first one is the well-known type inference. I will not cover it on this post but this is a great feature of the Scala compiler that can often infer the type of expression/variable. You almost don’t have to specify the type of variable when you’re declaring it.

The second one which appears to be less known (as it’s more hidden I think) is the concept of implicit conversions.

Take the following example :

scala> val numbers: Range = 1 until 1000 // same as 1.until(1000)
numbers: Range = Range 1 until 1000

What just happened here ? 1 is an Int but until is not defined in the Int class. So how did Scala succeed to understand that ?

When the Scala compiler encountered the method until applied on 1 being an Int, it did an implicit conversion from an Int to a RichInt because until applies on a RichInt type.

This is one of the Scala compiler features, it can detect and convert given arguments types to match the wanted expression.

In the example we wanted to apply the method until on a number, the compiler searched an implicit conversion that could take this number and return something where until is defined, in this case a RichInt.

Debug implicits conversions

To confirm the previous example behavior, we can see what the Scala compiler generates when it parses our code to then transform it into JVM byte code. We can plug our debug at different steps of this process phases. The one that interests us is the typer phase, as it’s during this phase that implicit conversions are applied.

Let’s put our initial example inside a valid Scala file named Test.scala :

// Test.scala
object Test {
  def main(args: Array[String]): Unit = {
    val numbers: Range = 1 until 10
  }
}

Now let’s compile it with debug enabled on typer phase with this command :

$ scalac -Xprint:typer Test.scala

The compiler displays what is the state of the generated code at the end of the phase :

1
2
3
4
5
6
7
8
9
10
11
12
13
[[syntax trees at end of                     typer]] // Test.scala
package <empty> {
  object Test extends scala.AnyRef {
    def <init>(): Test.type = {
      Test.super.<init>();
      ()
    };
    def main(args: Array[String]): Unit = {
      val numbers: scala.collection.immutable.Range = scala.Predef.intWrapper(1).until(10);
      ()
    }
  }
}

Alongside boilerplate code, the most interesting part is at line 9, when we can see that our initial 1 is now a scala.Predef.intWrapper(1).

If we look at the source code of Predef.scala in the Scala API, we can find this method defined as : implicit def intWrapper(x: Int) = new runtime.RichInt(x).

Our assumption was true, the compiler understood our code and applied the conversion.

Note: more informations about compiler phases here : https://typelevel.org/scala/docs/phases.html

Import implicits conversions

If we look again at Predef.scala file. We can see that Scala has many implicit conversions predefined and enabled by default.

We can also explicitly import conversions defined in the rest of Scala API (but not enabled by default), like ones defined in the Duration utility class, as below :

scala> import scala.concurrent.duration.DurationInt
import scala.concurrent.duration.DurationInt

scala> val time = 180 seconds
time: scala.concurrent.duration.FiniteDuration = 180 seconds

scala> time.toMinutes
res8: Long = 3

Create implicits conversions

Creating a new implicit conversion is as simple as defining a method with the implicit keyword and having it available in the scope where we want to apply the conversion :

scala> implicit def intToString(x: Int) = x.toString
intToString: (x: Int)String

scala> val a: String = 11
a: String = 11

This example was straightforward, a more interesting and useful example is to create an implicit class to provide new methods on a Type :

object MathImprovements {
  implicit class AlmostEquals(val d: Double) {
    def ~=(d2: Double): Boolean = (d - d2).abs <= 0.0001
  }
}

object Test {
  import MathImprovements.AlmostEquals

  def main(args: Array[String]): Unit = {
    print(1.0002 ~= 1.0001) // true
  }
}

Here the implicit class AlmostEquals provides a method ~= on a Double type to see if it has the same first 3 decimals with another Double. Definition of AlmostEquals is the same as a normal class definition, plus the implicit keyword.

Note: an implicit class must live either in another class, object or trait, but not at the top level. This is a restriction in order to make their usages explicit by importing them where needed.

Conclusion

Implicit conversions (and implicits in general) is one of the main feature of Scala. They are mainly used to add new functionalities on Types which are closed by design. I would recommend to use them with precaution as they can make the code harder to debug without a good tooling like an IDE. But they are a good way to reduce boilerplate code and increase readability.