• Rally
  • Implicit Implications (part 2): Implicit Conversions

Implicit Implications (part 2): Implicit Conversions

By Jeff May | February 21, 2018

Last week, I covered implicit parameters, Scala’s mechanism for implementing type classes and context passing.

This week, I will cover the other form of implicits supported by Scala, called implicit conversions. This is a bit more of a controversial feature of the language. It can be (and has been) abused to make code less type-safe and / or confusing. However, it has some killer use cases that make it an essential tool for the advanced Scala developer. It is primarily a tool for adapting code that you don’t own to make it source-code compatible with your preferred interface. This can be useful if you want to separate dependencies into separate artifacts without forcing source code to explicitly convert the types they are using to adapt to the interface changes. So let’s get into the nitty gritty of how it works…

Implicit conversions

Scala will trigger an implicit lookup in one other situation, called implicit conversion. If you attempt to access a value or method on an object whose type does not provide that member, the compiler will search for a method or function that takes a value of that type and produces an object with the member you are attempting to access. In addition, the compiler will also look for an implicit conversion when attempting to call a method by passing an argument of type A when it expects type B. The compiler will search in scope to find an implicit function or method from A => B.

This feature enables the following language semantics:

  • Type Conversion
  • Extension Methods

Both type conversion and extension methods are mechanisms of altering the syntax or semantics of a library that you do not have control over. In the case of Scala, this is very important for Java interoperability.

For example, you will often want to convert between Java collections and Scala collections. It turns out that type conversion is actually not a great mechanism for this (I’ll explain later), but because it is still part of the standard library, I will demonstrate how this works:

package some.awesome;
class Awesome {
 public static <T> void method(List<T> items) {
 // something super useful goes here
 }
}
import some.awesome.Awesome
object ScalaAwesome {
 import scala.collection.convert.WrapAsJava._
 def coolStuff[T](items: Seq[T]): Unit = {
 Awesome.method(items) // items is implicitly converted from a scala Seq to a java List
 // do other stuff, potentially calling other methods that you need to convert from Scala collections to Java
 }
}

This is pretty straight forward. It allows you to pretend that you are operating with the same interface as you would if you were programming with Java, except you can do it in Scala using simple Java wrappers.

Implicit Danger

The problem arises when you consider the many ways one can abuse implicit conversions. For example, this is a Scala puzzler that Martin Odersky brought up at Scala Days 2017 in his talk on implicits:

implicit val colors: Seq[String] = Seq("red", "blue", "green")
object Printer {
 def print(s: String) = println(s)
}
Printer.print(42)

And what does this code do? Why throw an exception of course!

java.lang.IndexOutOfBoundsException: 42
 at scala.collection.LinearSeqOptimized.apply(LinearSeqOptimized.scala:63)
 at scala.collection.LinearSeqOptimized.apply$(LinearSeqOptimized.scala:61)
 at scala.collection.immutable.List.apply(List.scala:86)
 ... 29 elided

This is because Seq[T] extends Int => T, which is the precise signature that Scala needs to convert the Int 42 into a String, so it checks the index 42 of the implicit function named colors and BOOM!

The best practice here is to never define implicit conversions on primitive or collection types. In fact, Scala has deemed that implicit conversions are so dangerous, they require you to import scala.language.implicitConversions to use them (if you have the -feature compiler flag turned on).

The Scala standard library developers decided to follow this newfound best practice and implemented an alternative (and preferred) way to convert Scala collections into Java. This is using an extension method. Extension methods are a way to add methods or values to an object after it has been instantiated with a fixed type.

So basically, instead of converting directly from a Scala Seq[A] to a Java List<A>, you import an implicit conversion from Seq[T] => { def asJava: java.util.List[T] } and then call .asJava on your Seq[A]:

import some.awesome.Awesome
object ScalaAwesome {
 import scala.collection.JavaConverters._
 def coolStuff[T](items: Seq[T]): Unit = {
 Awesome.method(items.asJava) // Now you call .asJava explicitly on the collection
 // do other stuff, potentially calling other methods that you need to convert from Scala collections to Java
 }
}

To see how this is implemented with *extension methods*, we will look at implicit classes.

Implicit Classes

Many languages have some way to extend existing types with extension methods (for example C#, Kotlin, and Rust), but Scala goes one step further by allowing you to define a new type and mixin traits (late-trait inheritance) as well as add single methods. This is really just a special case of implicit conversion.

trait Closable {
 def close(): Unit
}
implicit class LateTraitOperations(sys: ActorSystem) extends Closable {
 override def close(): Unit = sys.stop()
 def extensionMethod(): Unit = {
 println(“You can combine trait inheritance AND extension methods”)
 }
}

This is a very powerful pattern that avoids some of the nastier edge cases of implicit conversions, so it has been given its own syntax, called implicit class. There are some restrictions to implicit class. For example, you can only define it inside of an object, package, or package object. It cannot live by itself. Additionally, this syntax does not require importing scala.language.implicitConversions because, in general, it is much safer. You have to reference a method or value explicitly to trigger the conversion.

implicit class RubyInt(value: Int) {
 def times(block: => Unit): Unit = {
 for (_ <- 1 to value) {
 block // execute the given function
 }
 }
}
// implicitly convert Int into RubyInt and call the "times" method
2 times {
 println("Clap!")
 Thread.sleep(600)
 println("Hey!")
 Thread.sleep(1200)
 println("Hey!")
 Thread.sleep(600)
}
4 times {
 println("Clap!")
 Thread.sleep(300)
}
8 times {
 println("Clap!")
 Thread.sleep(150)
}
16 times {
 println("Clap!")
 Thread.sleep(75)
}
println("D-D-D-Drop the base!")

As you can see, we just created a new notation for making Trap music! Awesome!

The compiler will essentially compile that into:

// just a normal class
class RubyInt(value: Int) {
 def times(block: => Unit): Unit = {
 for (_ <- 1 to value) {
 block // execute the given function
 }
 }
}
// the implicit conversion
implicit def RubyInt(value: Int): RichInt = new RichInt(value)
RubyInt(2).times {
 // ...
}
// ... and so on ...

Well that’s nice, but we’ve paid for that syntax by instantiating an object for every invocation. Even worse, it has to box the Int primitive.

There is also a way to do this without paying the cost of instantiation using the power of value classes! I won’t go into detail, you can read more about them here.

You just redefine your implicit class by requiring a single val in the constructor and extending AnyVal.

implicit class RubyInt(val value: Int) extends AnyVal {
 def times(block: => Unit): Unit = {
 // same as above...
 }
}

And now the code desugars into:

object RubyInt {
 // static method call
 final def times(value: Int, block: => Unit): Unit = {
 // same as above...
 }
}
RubyInt.times(2, {
 // ...
})
// ... and so on ...

The restrictions for extending AnyVal can be cumbersome. Specifically, you can only define a value class at the top level or inside of an object singleton.

Okay, so that about wraps up both ways to invoke an implicit conversion.

What are the implications of all this?

Okay, so implicits have their usages and their rough edges. How do I know when to use them?

Implicit conversions are very dangerous. In my view, the only really good use case for implicit conversions is to support extension methods / lazy trait inheritance. If you are making one type “look” like another type, you are making Scala look bad and are probably violating type safety.

Implicits are a distinct feature of Scala that are easy to abuse, but implicits have been given a lot of attention in Dotty, the compiler that will be used for Scala 3. Next week, I will go over the future of Scala and how the rough edges of implicits will be smoothed out and the power and orthogonality of implicits will enable a whole new style of programming in Scala. Stay tuned…

Jeff May

Keep Exploring

Would you like to see more? Explore