Mike Slinn

Implicit Classes

— Draft —

Published 2013-09-18. Last modified 2019-07-23.
Time to read: 6 minutes.

Implicit classes provide extra functionality to value classes.

The sample code for this lecture can be found in courseNotes/src/main/scala/ImplicitClasses.scala.

Implicit classes allow the addition of extra functionality onto an existing type without actually modifying the original type. Implicit classes allow business logic to be defined outside of value objects, and provides a form of dependency injection. Implicit classes are just like regular Scala classes, with the following additional rules.

  1. Implicit classes are decorated with the implicit keyword.
  2. Implicit classes must not be top-level objects. You must define them in another object or class.
  3. Implicit class constructors must receive at least one parameter.
  4. The only methods in implicit classes that will be considered for implicit conversions are those that do not accept any parameters.

For an alternative to using implicit classes to enrich existing types, see the Parametric Traits as Rich Interfaces section of the Parametric Types lecture.

Simple Example

In the following simple example, an implicit class is defined which wraps an Int. Note that the name of this class is never directly referenced in your program because it is implicitly referenced. This means that the name of the class is completely arbitrary. In this example, the length method returns the length of the string representation of the Int that is passed in. From the point of view of the programmer when this implicit class is in scope, it seems as if Ints now have a method called length! However, please note that the definition of Int has not changed. All that happens is that the Scala compiler looks around for implicit classes whenever it cannot find a method – rather like Ruby’s method_missing mechanism.

I called this class randomName to emphasize that its name does not matter. The constructor for this class accepts an Int. The class defines a method called length which returns the number of digits of the int variable.

Scala REPL
scala> implicit class randomName(int: Int) {
     |   val length: Int = int.toString.length
     | }
defined class randomName

Now that the implicit class is defined in the current scope, we can invoke the length method on any Int.

Scala REPL
scala> 5.length
res4: Int = 1
scala>
55.length res4: Int = 2
scala>
555.length res4: Int = 3

At compile time, when the compiler encountered a reference to the length method, it ’knew’ that no such method is defined for the Int type. However, the compiler was aware that the implicit class definition for randomName was in scope at that point in the program, so at runtime an instance of randomName was implicitly created, the value of the Int was passed to it, and the randomName.length method was invoked.

We use the term boxing to denote the process of creating an instance of a holder class such as randomName so we can pass a value and invoke a method, and we use the term unboxing to denote the extraction of the value and the disposing of the holder class.

BTW, notice how I defined the length property as a val, because its value does not change after a randomName instance is created.

Implicit Value Classes

You can define an implicit class as a value class and possibly eliminate boxing and unboxing, if value class constraints are not a problem for your use case. We discussed value classes in the Implicit Values lecture. All we have to do in order to make the implicit class more efficient at runtime is to make it into a value class by turning each of the constructor parameters into class instance properties (which is what the val is for), and to have the class extend AnyVal.

Scala REPL
scala> implicit class randomName2(val long: Long) extends AnyVal {
     |   @inline def length: Int = long.toString.length
     | }
defined class randomName2
scala>
5L.length res4: Int = 1

BTW, you might have been motivated to define the length property as a val, because its value does not change after a randomName instance is created. However, as we learned in the Implicit Values lecture, implicit classes are not allowed to contain vals or vars, so I defined it as an @inline def. @inline causes the compiler to emit the JVM byte code from the method definition is emitted each time the method is invoked, instead of making a method call. @inline increases the size of the program slightly, but it runs faster.

Example: IntToUSD

This example has several interesting features that make it lightweight and easy to use.

  • The implicit class extends AnyVal so the IntToUSD class need not be instantiated for many / most circumstances.
  • The default method apply is used so no method name need be specified.
  • @inline is again used to trade off execution speed for program size.
Scala REPL
scala> implicit class IntToUSD(val i: Int) extends AnyVal {
     |    @inline def apply(s: String): String = s"$i $s"
     |  }
defined class IntToUSD
scala>
100("USD") res5: String = 100 USD

This is the same as writing.

Scala REPL
scala> IntToUSD(100).apply("USD")
res2: String = 100 USD 

Example: SymbolToCurrency

In Scala, a Symbol is like a String, but instead of being surrounded by double quotes, until Scala 2.13 it was simply preceded by a single quote. That means Symbol instances do not normally have embedded spaces. Starting with Scala 2.13, the Symbol.apply default method is the preferred way to create Symbol instances. A symbol can be converted to a String by accessing its name property.

Scala REPL
scala> implicit class SymbolToCurrency(val sym: Symbol) extends AnyVal {
     |   @inline def apply(value: Int): String = s"$value ${ sym.name }"
     | }
defined class SymbolToCurrency
scala>
Symbol("USD")(100) res6: String = 100 USD

This is the same as writing:

Scala REPL
scala> SymbolToCurrency(Symbol("USD")).apply(100)
res7: String = 100 USD 

Exercise – What does this print out?

Scala code
object AppleFanBoi extends App {
  implicit class IosInt(val i: Int) extends AnyVal { def s: Int = i + 1 }
println(s"I have an iPhone ${4.s}") }

Solution

You can run this program by typing:

Scala REPL
$ scala> sbt "runMain solutions.AppleFanBoi"
I have an iPhone 5

“Enhance My Library” / “Extension Method” Pattern

This pattern has been known by several names, including the original (and offensive) "Pimp my library" name.

The functionality defined by Scala’s built-in value types are extended by Scala’s implicit conversions. Some people claim that the use of this pattern is ’the Scala way’, and everyone should consider using to add functionality to domain model classes. This keeps your model separate from your business logic. The downside of using this pattern is that debugging implicit classes is more difficult.

Let’s see how this works by extending the Dog case class we used earlier, and adding classes Stick and Ball. I defined DogCommands as an implicit value class because it only contains methods and no variables.

Scala code
case class Dog(name: String) {
  override def equals(that: Any): Boolean = canEqual(that) && hashCode==that.hashCode
  override def hashCode = name.hashCode
}
class Stick
case class Ball(color: String)
implicit class DogCommands(val dog: Dog) extends AnyVal { def call(me: String): String = s"Here, ${dog.name} come to $me"
def fetch(stick: Stick): String = s"${dog.name}, fetch the stick!"
def fetch(ball: Ball): String = s"${dog.name}, fetch the ${ball.color} ball!" }

We can use the implicit class this way:

Scala REPL
scala> val dog = Dog("Fido")
dog: Dog = Dog(Fido)
scala>
dog.call("me") res2: String = Here, Fido come to me
scala>
dog.fetch(new Stick) res3: String = Fido, fetch the stick!
scala>
val ball = new Ball("green") ball: Ball = Ball(green)
scala>
dog.fetch(ball) res4: String = Fido, fetch the green ball!

This is similar to how Scala’s RichInt class (invisible for Scala 2.13.0, hopefully will be fixed for 2.13.1 by moving it to another package) extends the functionality of the Int value class using implicits.

You can also run this program like this:

Shell
$ sbt "runMain EnhanceMyLibrary"

The Defining Custom String Interpolation to Access a Map section of the Collections Overview lecture shows another example of enriching a class.

Exercise - Enhance Complex

Use the Enhance My Library pattern to add an absolute value method to Complex. Compute this value by taking the square root of the square of the real part plus the square of the imaginary part.

Hint

Use math.sqrt to take the square root.

Solution

Shell
package solutions
object ImplicitClasses extends App { import solutions.ImplicitCoercion.Complex
implicit class RichComplex(val complex: Complex) extends AnyVal { def abs: Double = math.sqrt(complex.re * complex.re + complex.im * complex.im) }
println(s"Complex(3, 4).abs=${Complex(3, 4).abs}") }

You can run this solution by typing.

Shell
$ sbt "runMain solutions.ImplicitClasses"
Complex(3, 4).abs=5.0 

Exercise – Separating Business Logic from the Domain Model

Define a domain class Weather that somehow describes the current state of the weather. Define an implicit class RichWeather that provides actions like rain(), sunshine() and hurricane(). Those methods should change the current state of the weather and print a suitable message. Run the solution as a console program.

Solution

Scala code
package solutions
object WeatherMain extends App { case class Weather(var status: String)
implicit class RichWeather(weather: Weather) { def genericOp(newStatus: String): Weather = { weather.status = newStatus println(s"It is now ${weather.status}") weather }
def rain(): Weather = genericOp("raining")
def hail(): Weather = genericOp("hailing")
def sunshine(): Weather = genericOp("shining") }
val weather = Weather("shining") weather.rain() weather.hail() weather.sunshine() }

To run, type:

Shell
$ sbt "runMain solutions.WeatherMain"
It is now raining
It is now hailing
It is now shining 

Where to Put Implicit Definitions?

Enriched classes are typically defined in a separate package from the value objects that they enrich. This is also true for implicit conversions. Normally you should limit the scope of implicit definitions. If the implicits are merely required for a class or trait, then define them in the companion object and import them. If the implicits are required for an entire package, they could be defined in a package object. However if the implicit definitions were also required in other packages you might not want to drag in the other definitions in the package object. Instead you can make the implicits available as required by defining them within another object and by explicitly importing them when required. You can also define implicits in traits that are mixed in as required.

There are a variety of ways to set this up. Implicit classes were introduced with Scala 2.10; prior to that the same capability required a more complex setup to achieve a similar result. Lets look at the source code of a few large open-source projects to see how this was done; the links will take you to the source code on GitHub.

  1. The Scala runtime uses the Predef object as the gathering point for numerous default implicits. Predef is automatically included into every Scala program.
    Scala code
    object Predef extends LowPriorityImplicits
    The pattern used in Predef was set up in Scala 2.8, before implicit classes were introduced, so the mechanics used are more complex than is required today. Predef defines explicit conversions that wrap the type to be enriched, like this:
    Scala code
    @inline implicit def intWrapper(x: Int) = new runtime.RichInt(x)
    Note that the conversion is marked as @inline, which means that this acts more like a C-language macro expansion than a method call.
  2. scala.jdk.CollectionConverters, discussed in the To Converters lecture, follows a pattern used when there are many implicit conversions; AsJavaExtensions with AsScalaExtensions actually define the implicits:
    Scala code
    object CollectionConverters extends AsJavaExtensions with AsScalaExtensions

Comprehensive Example (Supplemental)

This example defines an implicit class called SymbolLookup which converts Scala Symbols to currency exchange rates. A Symbol is like a unique string, and is often written by prefacing it with a single quote. Symbols should not be defined with embedded spaces. Once the implicit class is explicitly imported it can be used as follows.

Scala code
object Rates extends App {
  import SymbolToCurrency._
println(Symbol("CAD")(100.0)) println(’USD(100.0)) println(’JPY(100.0)) println(’BLA(100.0)) }

Typical output is:

Output
126.1808 CAD
100.0 USD
11941.849999999999 JPY
Currency BLA unknown.
Available currencies are: AED, AFN, ALL, AMD, ANG, AOA, ARS, AUD, AWG, AZN, BAM, BBD, BDT, BGN, BHD, BIF, BMD, BND, BOB, BRL, BSD, BTC, BTN, BWP, BYR, BZD, CAD, CDF, CHF, CLF, CLP, CNY, COP, CRC, CUC, CUP, CVE, CZK, DJF, DKK, DOP, DZD, EEK, EGP, ERN, ETB, EUR, FJD, FKP, GBP, GEL, GGP, GHS, GIP, GMD, GNF, GTQ, GYD, HKD, HNL, HRK, HTG, HUF, IDR, ILS, IMP, INR, IQD, IRR, ISK, JEP, JMD, JOD, JPY, KES, KGS, KHR, KMF, KPW, KRW, KWD, KYD, KZT, LAK, LBP, LKR, LRD, LSL, LTL, LVL, LYD, MAD, MDL, MGA, MKD, MMK, MNT, MOP, MRO, MTL, MUR, MVR, MWK, MXN, MYR, MZN, NAD, NGN, NIO, NOK, NPR, NZD, OMR, PAB, PEN, PGK, PHP, PKR, PLN, PYG, QAR, RON, RSD, RUB, RWF, SAR, SBD, SCR, SDG, SEK, SGD, SHP, SLL, SOS, SRD, STD, SVC, SYP, SZL, THB, TJS, TMT, TND, TOP, TRY, TTD, TWD, TZS, UAH, UGX, USD, UYU, UZS, VEF, VND, VUV, WST, XAF, XAG, XAU, XCD, XDR, XOF, XPF, YER, ZAR, ZMK, ZMW, ZWL

Here is the implicit class defined inside the SymbolToCurrency object:

Scala code
implicit class SymbolLookup(val symbol: Symbol) extends AnyVal {
  @inline def apply(value: Double): String = try {
    val convertedValue = value * rateMap(symbol)
    s"$convertedValue ${symbol.name}"
  } catch {
    case nsee: NoSuchElementException =>
      println(s"Currency ${symbol.name} unknown.
Available currencies are: ${rateMap.keys.map(_.name).toSeq.sorted.mkString(", ")}")
      ""
  }
}

This code relies on the OpenExchangeRates.org API, which returns JSON. You will need your own OpenExchange key in order to run this code example; there is no charge for a developer key. Because we don’t cover JSON in this course, I used a regular expression (Regex) to parse the JSON. The Scala I/O lecture will explain how the io.Source.fromURL method works.

Scala code
protected val openExchangeKey = sys.env("OPEN_EXCHANGE_KEY")
protected val urlStr = s"https://openexchangerates.org/api/latest.json?app_id=$openExchangeKey"
protected val latestRates: String = io.Source.fromURL(urlStr).mkString
protected val RateRegex = """(?s)rates.: \{(.*?)\}""".r.unanchored
protected val RateRegex(rates) = latestRates

rates is a String, which is parsed into a List[(Symbol, Double)], and is stored as rateTuples. The For-Loops and For-Comprehensions and For-Loops and For-Comprehensions Examples lectures will explain the for-comprehension.

Scala code
protected val rateTuples: List[(Symbol, Double)] = (for {
  rateStr <- rates.split(",").toList
} yield {
  rateStr.replaceAll("[ \n\"]", "").split(":") match {
    case Array(k, v) =>
      try {
        Some(Symbol(k) -> v.toDouble)
      } catch {
        case e: Exception =>
          println(s"${e.getClass.getName} while parsing $v: ${e.getMessage}; ignored")
          None
      }
case wat => println(s"$wat could not be parsed, ignored") None } }).flatten

rateMap is then created from rateTuples, and is now available for lookups using the default method apply. Maps will be discussed in the Collections Overview lecture.

Scala code
protected val rateMap: Map[Symbol, Double] = rateTuples.toMap
def apply(symbol: Symbol): Double = rateMap(symbol)

To run, type:

Shell
$ export OPEN_EXCHANGE_KEY=7364734638483498732987423 // this is a bogus key
$ sbt "runMain Rates"

Output is as shown above.


* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.