The Artima Developer Community

Chapter 21 of Programming in Scala, First Edition
Implicit Conversions and Parameters
by Martin Odersky, Lex Spoon, and Bill Venners
December 10, 2008

There's a fundamental difference between your own code and libraries of other people: you can change or extend your own code as you wish, but if you want to use someone else's libraries, you usually have to take them as they are.

A number of constructs have sprung up in programming languages to alleviate this problem. Ruby has modules, and Smalltalk lets packages add to each other's classes. These are very powerful, but also dangerous, in that you modify the behavior of a class for an entire application, some parts of which you might not know. C# 3.0 has static extension methods, which are more local, but also more restrictive in that you can only add methods, not fields, to a class, and you can't make a class implement new interfaces.

Scala's answer is implicit conversions and parameters. These can make existing libraries much more pleasant to deal with by letting you leave out tedious, obvious details that obscure the interesting parts of your code. Used tastefully, this results in code that is focused on the interesting, non-trivial parts of your program. This chapter shows you how implicits work, and presents some of the most common ways they are used.

21.1 Implicit conversions [link]

Before delving into the details of implicit conversions, take a look at a typical example of their use. One of the central collection traits in Scala is RandomAccessSeq[T], which describes random access sequences over elements of type T. RandomAccessSeqs have most of the utility methods that you know from arrays or lists: take, drop, map, filter, exists, and mkString are just some examples. To make a new random access sequence, all you must do is extend trait RandomAccessSeq. You only need to define two methods that are abstract in the trait: length and apply. You then get implementations of all the other useful methods in the trait "for free."

So far so good. This works fine if you are about to define new classes, but what about existing ones? Maybe you'd like to also treat classes in other people's libraries as random access sequences, even if the designers of those libraries had not thought of making their classes extend RandomAccessSeq. For instance, a String in Java would make a fine RandomAccessSeq[Char], except that unfortunately Java's String class does not inherit from Scala's RandomAccessSeq trait.

In situations like this, implicits can help. To make a String appear to be a subtype of RandomAccessSeq, you can define an implicit conversion from String to an adapter class that actually is a subtype of RandomAccessSeq:

  implicit def stringWrapper(s: String) = 
    new RandomAccessSeq[Char] {
      def length = s.length
      def apply(i: Int) = s.charAt(i)
    }
That's it.[1] The implicit conversion is just a normal method. The only thing that's special is the implicit modifier at the start. You can apply the conversion explicitly to transform Strings to RandomAccessSeqs:
  scala> stringWrapper("abc123") exists (_.isDigit)
  res0: Boolean = true
But you can also leave out the conversion and still get the same behavior:
  scala> "abc123" exists (_.isDigit)
  res1: Boolean = true
What goes on here under the covers is that the Scala compiler inserts the stringWrapper conversion for you. So in effect it rewrites the last expression above to the one before. But on the surface, it's as if Java's Strings had acquired all the useful methods of trait RandomAccessSeq.

This aspect of implicits is similar to extension methods in C#, which also allow you to add new methods to existing classes. However, implicits can be far more concise than extension methods. For instance, we only needed to define the length and apply methods in the stringWrapper conversion, and we got all other methods in RandomAccessSeq for free. With extension methods you'd need to define every one of these methods again. This duplication makes code harder to write, and, more importantly, harder to maintain. Imagine someone adds a new method to RandomAccessSeq sometime in the future. If all you have is extension methods, you'd have to chase down all RandomAccessSeq "copycats" one by one, and add the new method in each. If you forget one of the copycats, your system would become inconsistent. Talk about a maintenance nightmare! By contrast, with Scala's implicits, all conversions would pick up the newly added method automatically.

Another advantage of implicit conversions is that they support conversions into the target type, a type that's needed at some point in the code. For instance, suppose you write a method printWithSpaces, which prints all characters in a given random access sequence with spaces in between them:

  def printWithSpaces(seq: RandomAccessSeq[Char]) = 
    seq mkString " "
Because Strings are implicitly convertible to RandomAccessSeqs, you can pass a string to printWithSpaces:
  scala> printWithSpaces("xyz")
  res2: String = x y z
The last expression is equivalent to the following one, where the conversion shows up explicitly:
  scala> printWithSpaces(stringWrapper("xyz"))
  res3: String = x y z

This section has shown you some of the power of implicit conversions, and how they let you "dress up" existing libraries. In the next sections you'll learn the rules that determine when implicit conversions are tried and how they are found.

21.2 Rules for implicits [link]

Implicit definitions are those that the compiler is allowed to insert into a program in order to fix any of its type errors. For example, if x + y does not type check, then the compiler might change it to convert(x) + y, where convert is some available implicit conversion. If convert changes x into something that has a + method, then this change might fix a program so that it type checks and runs correctly. If convert really is just a simple conversion function, then leaving it out of the source code can be a clarification.

Implicit conversions are governed by the following general rules:

Marking Rule: Only definitions marked implicit are available. The implicit keyword is used to mark which declarations the compiler may use as implicits. You can use it to mark any variable, function, or object definition. Here's an example of an implicit function definition:[2]

  implicit def intToString(x: Int) = x.toString
The compiler will only change x + y to convert(x) + y if convert is marked as implicit. This way, you avoid the confusion that would result if the compiler picked random functions that happen to be in scope and inserted them as "conversions." The compiler will only select among the definitions you have explicitly marked as implicit.

Scope Rule: An inserted implicit conversion must be in scope as a single identifier, or be associated with the source or target type of the conversion. The Scala compiler will only consider implicit conversions that are in scope. To make an implicit conversion available, therefore, you must in some way bring it into scope. Moreover, with one exception, the implicit conversion must be in scope as a single identifier. The compiler will not insert a conversion of the form someVariable.convert. For example, it will not expand x + y to someVariable.convert(x) + y. If you want to make someVariable.convert available as an implicit, therefore, you would need to import it, which would make it available as a single identifier. Once imported, the compiler would be free to apply it as convert(x) + y. In fact, it is common for libraries to include a Preamble object including a number of useful implicit conversions. Code that uses the library can then do a single "import Preamble._" to access the library's implicit conversions.

There's one exception to the "single identifier" rule. The compiler will also look for implicit definitions in the companion object of the source or expected target types of the conversion. For example, if you're attempting to pass a Dollar object to a method that takes a Euro, the source type is Dollar and the target type is Euro. You could, therefore, package an implicit conversion from Dollar to Euro in the companion object of either class, Dollar or Euro. Here's an example in which the implicit definition is placed in Dollar's companion object:

  object Dollar {
    implicit def dollarToEuro(x: Dollar): Euro = ...
  }
  class Dollar { ... }
In this case, the conversion dollarToEuro is said to be associated to the type Dollar. The compiler will find such an associated conversion every time it needs to convert from an instance of type Dollar. There's no need to import the conversion separately into your program.

The Scope Rule helps with modular reasoning. When you read code in a file, the only things you need to consider from other files are those that are either imported or are explicitly referenced through a fully qualified name. This benefit is at least as important for implicits as for explicitly written code. If implicits took effect system-wide, then to understand a file you would have to know about every implicit introduced anywhere in the program!

Non-Ambiguity Rule: An implicit conversion is only inserted if there is no other possible conversion to insert. If the compiler has two options to fix x + y, say using either convert1(x) + y or convert2(x) + y, then it will report an error and refuse to choose between them. It would be possible to define some kind of "best match" rule that prefers some conversions over others. However, such choices lead to really obscure code. Imagine the compiler chooses convert2, but you are new to the file and are only aware of convert1—you could spend a lot of time thinking a different conversion had been applied!

In cases like this, one option is to remove one of the imported implicits so that the ambiguity is removed. If you prefer convert2, then remove the import of convert1. Alternatively, you can write your desired conversion explicitly: convert2(x) + y.

One-at-a-time Rule: Only one implicit is tried. The compiler will never rewrite x + y to convert1(convert2(x)) + y. Doing so would cause compile times to increase dramatically on erroneous code, and it would increase the difference between what the programmer writes and what the program actually does. For sanity's sake, the compiler does not insert further implicit conversions when it is already in the middle of trying another implicit. However, it's possible to circumvent this restriction by having implicits take implicit parameters, which will be described later in this chapter.

Explicits-First Rule: Whenever code type checks as it is written, no implicits are attempted. The compiler will not change code that already works. A corollary of this rule is that you can always replace implicit identifiers by explicit ones, thus making the code longer but with less apparent ambiguity. You can trade between these choices on a case-by-case basis. Whenever you see code that seems repetitive and verbose, implicit conversions can help you decrease the tedium. Whenever code seems terse to the point of obscurity, you can insert conversions explicitly. The amount of implicits you leave the compiler to insert is ultimately a matter of style.

Naming an implicit conversion. Implicit conversions can have arbitrary names. The name of an implicit conversion matters only in two situations: if you want to write it explicitly in a method application, and for determining which implicit conversions are available at any place in the program.

To illustrate the second point, say you have an object with two implicit conversions:

  object MyConversions {
    implicit def stringWrapper(s: String):
        RandomAccessSeq[Char] = ...
    implicit def intToString(x: Int): String = ...
  }
In your application, you want to make use of the stringWrapper conversion, but you don't want integers to be converted automatically to strings by means of the intToString conversion. You can achieve this by importing only one conversion, but not the other:
  import MyConversions.stringWrapper
  ... // code making use of stringWrapper
In this example, it was important that the implicit conversions had names, because only that way could you selectively import one and not the other.

Where implicits are tried. There are three places implicits are used in the language: conversions to an expected type, conversions of the receiver of a selection, and implicit parameters. Implicit conversions to an expected type let you use one type in a context where a different type is expected. For example, you might have a String and want to pass it to a method that requires a RandomAccessSeq[Char]. Conversions of the receiver let you adapt the receiver of a method call, i.e., the object on which a method is invoked, if the method is not applicable on the original type. An example is "abc".exists, which is converted to stringWrapper("abc").exists because the exists method is not available on Strings but is available on RandomAccessSeqs. Implicit parameters, on the other hand, are usually used to provide more information to the called function about what the caller wants. Implicit parameters are especially useful with generic functions, where the called function might otherwise know nothing at all about the type of one or more arguments. Each of the following three sections will discuss one of these three kinds of implicits.

21.3 Implicit conversion to an expected type [link]

Implicit conversion to an expected type is the first place the compiler will use implicits. The rule is simple. Whenever the compiler sees an X, but needs a Y, it will look for an implicit function that converts X to Y. For example, normally a double cannot be used as an integer, because it loses precision:

  scala> val i: Int = 3.5
  <console>:5: error: type mismatch;
   found   : Double(3.5)
   required: Int
         val i: Int = 3.5
                      ^

However, you can define an implicit conversion to smooth this over:

  scala> implicit def doubleToInt(x: Double) = x.toInt
  doubleToInt: (Double)Int
  
scala> val i: Int = 3.5 i: Int = 3
What happens here is that the compiler sees a Double, specifically 3.5, in a context where it requires an Int. So far, the compiler is looking at an ordinary type error. Before giving up, though, it searches for an implicit conversion from Double to Int. In this case, it finds one: doubleToInt, because doubleToInt is in scope as a single identifier. (Outside the interpreter, you might bring doubleToInt into scope via an import or possibly through inheritance.) The compiler then inserts a call to doubleToInt automatically. Behind the scenes, the code becomes:
  val i: Int = doubleToInt(3.5)
This is literally an implicit conversion. You did not explicitly ask for conversion. Instead, you marked doubleToInt as an available implicit conversion by bringing it into scope as a single identifier, and then the compiler automatically used it when it needed to convert from a Double to an Int.

Converting Doubles to Ints might raise some eyebrows, because it's a dubious idea to have something that causes a loss in precision happen invisibly. So this is not really a conversion we recommend. It makes much more sense to go the other way, from some more constrained type to a more general one. For instance, an Int can be converted without loss of precision to a Double, so an implicit conversion from Int to Double makes sense. In fact, that's exactly what happens. The scala.Predef object, which is implicitly imported into every Scala program, defines implicit conversions that convert "smaller" numeric types to "larger" ones. For instance, you will find in Predef the following conversion:

  implicit def int2double(x: Int): Double = x.toDouble
That's why in Scala Int values can be stored in variables of type Double. There's no special rule in the type system for this; it's just an implicit conversion that gets applied.[3]

21.4 Converting the receiver [link]

Implicit conversions also apply to the receiver of a method call, the object on which the method is invoked. This kind of implicit conversion has two main uses. First, receiver conversions allow smoother integration of a new class into an existing class hierarchy. And second, they support writing domain-specific languages (DSLs) within the language.

To see how it works, suppose you write down obj.doIt, and obj does not have a member named doIt. The compiler will try to insert conversions before giving up. In this case, the conversion needs to apply to the receiver, obj. The compiler will act as if the expected "type" of obj were "has a member named doIt." This "has a doIt" type is not a normal Scala type, but it is there conceptually and is why the compiler will insert an implicit conversion in this case.

Interoperating with new types

As mentioned previously, one major use of receiver conversions is allowing smoother integration of new with existing types. In particular, they allow you to enable client programmers to use instances of existing types as if they were instances of your new type. Take, for example, class Rational shown in Listing 6.5 here. Here's a snippet of that class again:

  class Rational(n: Int, d: Int) {
    ...
    def + (that: Rational): Rational = ...
    def + (that: Int): Rational = ...
  }

Class Rational has two overloaded variants of the + method, which take Rationals and Ints, respectively, as arguments. So you can either add two rational numbers or a rational number and an integer:

  scala> val oneHalf = new Rational(12)
  oneHalf: Rational = 1/2
  
scala> oneHalf + oneHalf res4: Rational = 1/1
scala> oneHalf + 1 res5: Rational = 3/2
What about an expression like 1 + oneHalf, however? This expression is tricky because the receiver, 1, does not have a suitable + method. So the following gives an error:

  scala> 1 + oneHalf
  <console>:6: error: overloaded method value + with
  alternatives (Double)Double <and> ... cannot be applied
  to (Rational)
       1 + oneHalf
         ^

To allow this kind of mixed arithmetic, you need to define an implicit conversion from Int to Rational:

  scala> implicit def intToRational(x: Int) = 
           new Rational(x, 1)
  intToRational: (Int)Rational
With the conversion in place, converting the receiver does the trick:
  scala> 1 + oneHalf
  res6: Rational = 3/2
What happens behind the scenes here is that Scala compiler first tries to type check the expression 1 + oneHalf as it is. This fails because Int has several + methods, but none that takes a Rational argument. Next, the compiler searches for an implicit conversion from Int to another type that has a + method which can be applied to a Rational. It finds your conversion and applies it, which yields:
  intToRational(1) + oneHalf
In this case, the compiler found the implicit conversion function because you entered its definition into the interpreter, which brought it into scope for the remainder of the interpreter session.

Simulating new syntax

The other major use of implicit conversions is to simulate adding new syntax. Recall that you can make a Map using syntax like this:

  Map(1 -> "one"2 -> "two"3 -> "three")
Have you wondered how the -> is supported? It's not syntax! Instead, -> is a method of the class ArrowAssoc, a class defined inside the standard Scala preamble (scala.Predef). The preamble also defines an implicit conversion from Any to ArrowAssoc. When you write 1 -> "one", the compiler inserts a conversion from 1 to ArrowAssoc so that the -> method can be found. Here are the relevant definitions:
  package scala
  object Predef {
    class ArrowAssoc[A](x: A) {
      def -> [B](y: B): Tuple2[A, B] = Tuple2(x, y)
    }
    implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] = 
      new ArrowAssoc(x)
    ...
  }
This "rich wrappers" pattern is common in libraries that provide syntax-like extensions to the language, so you should be ready to recognize the pattern when you see it. Whenever you see someone calling methods that appear not to exist in the receiver class, they are probably using implicits. Similarly, if you see a class named RichSomething, e.g., RichInt or RichString, that class is likely adding syntax-like methods to type Something.

You have already seen this rich wrappers pattern for the basic types described in Chapter 5. As you can now see, these rich wrappers apply more widely, often letting you get by with an internal DSL defined as a library where programmers in other languages might feel the need to develop an external DSL.

21.5 Implicit parameters [link]

The remaining place the compiler inserts implicits is within argument lists. The compiler will sometimes replace someCall(a) with someCall(a)(b), or new SomeClass(a) with new SomeClass(a)(b), thereby adding a missing parameter list to complete a function call. It is the entire last curried parameter list that's supplied, not just the last parameter. For example, if someCall's missing last parameter list takes three parameters, the compiler might replace someCall(a) with someCall(a)(b, c, d). For this usage, not only must the inserted identifiers, such as b, c, and d in (b, c, d), be marked implicit where they are defined, but also the last parameter list in someCall's or someClass's definition must be marked implicit.

Here's a simple example. Suppose you have a class PreferredPrompt, which encapsulates a shell prompt string (such as, say "$ " or "> ") that is preferred by a user:

  class PreferredPrompt(val preference: String)
Also, suppose you have a Greeter object with a greet method, which takes two parameter lists. The first parameter list takes a string user name, and the second parameter list takes a PreferredPrompt:
  object Greeter {
    def greet(name: String)(implicit prompt: PreferredPrompt) {
      println("Welcome, "+ name +". The system is ready.")
      println(prompt.preference)
    }
  }
The last parameter list is marked implicit, which means it can be supplied implicitly. But you can still provide the prompt explicitly, like this:
  scala> val bobsPrompt = new PreferredPrompt("relax> ")
  bobsPrompt: PreferredPrompt = PreferredPrompt@ece6e1
  
scala> Greeter.greet("Bob")(bobsPrompt)                     Welcome, Bob. The system is ready. relax> 

To let the compiler supply the parameter implicitly, you must first define a variable of the expected type, which in this case is PreferredPrompt. You could do this, for example, in a preferences object:

  object JoesPrefs {
    implicit val prompt = new PreferredPrompt("Yes, master> ")
  }
Note that the val itself is marked implicit. If it wasn't, the compiler would not use it to supply the missing parameter list. It will also not use it if it isn't in scope as a single identifier. For example:
  scala> Greeter.greet("Joe")
  <console>:7: error: no implicit argument matching parameter
    type PreferredPrompt was found.
         Greeter.greet("Joe")
                 ^

Once you bring it into scope via an import, however, it will be used to supply the missing parameter list:

  scala> import JoesPrefs._         
  import JoesPrefs._
  
scala> Greeter.greet("Joe") Welcome, Joe. The system is ready. Yes, master> 

Note that the implicit keyword applies to an entire parameter list, not to individual parameters. Listing 21.1 shows an example in which the last parameter list of Greeter's greet method, which is again marked implicit, has two parameters: prompt (of type PreferredPrompt) and drink (of type PreferredDrink):

  class PreferredPrompt(val preference: String)
  class PreferredDrink(val preference: String)
  
object Greeter {   def greet(name: String)(implicit prompt: PreferredPrompt,       drink: PreferredDrink) {
    println("Welcome, "+ name +". The system is ready.")     print("But while you work, ")     println("why not enjoy a cup of "+ drink.preference +"?")     println(prompt.preference)   } }
object JoesPrefs {   implicit val prompt = new PreferredPrompt("Yes, master> ")   implicit val drink = new PreferredDrink("tea") }
Listing 21.1 - An implicit parameter list with multiple parameters.

Singleton object JoesPrefs in Listing 21.1 declares two implicit vals, prompt of type PreferredPrompt and drink of type PreferredDrink. As before, however, so long as these are not in scope as single identifiers, they won't be used to fill in a missing parameter list to greet:

  scala> Greeter.greet("Joe"<console>:8: error: no implicit argument matching parameter
    type PreferredPrompt was found.
         Greeter.greet("Joe")
                 ^

You can bring both implicit vals into scope with an import:

  scala> import JoesPrefs._
  import JoesPrefs._
Because both prompt and drink are now in scope as single identifiers, you can use them to supply the last parameter list explicitly, like this:
  scala> Greeter.greet("Joe")(prompt, drink)
  Welcome, Joe. The system is ready.
  But while you work, why not enjoy a cup of tea?
  Yes, master> 
And because all the rules for implicit parameters are now met, you can alternatively let the Scala compiler supply prompt and drink for you by leaving off the last parameter list:
  scala> Greeter.greet("Joe")
  Welcome, Joe. The system is ready.
  But while you work, why not enjoy a cup of tea?
  Yes, master> 

One thing to note about the previous examples is that we didn't use String as the type of prompt or drink, even though ultimately it was a String that each of them provided through their preference fields. Because the compiler selects implicit parameters by matching types of parameters against types of values in scope, implicit parameters usually have "rare" or "special" enough types that accidental matches are unlikely. For example, the types PreferredPrompt and PreferredDrink in Listing 21.1 were defined solely to serve as implicit parameter types. As a result, it is unlikely that implicit variables of these types will be in scope if they aren't intended to be used as implicit parameters to Greeter.greet.

Another thing to know about implicit parameters is that they are perhaps most often used to provide information about a type mentioned explicitly in an earlier parameter list, similar to the type classes of Haskell. As an example, consider the maxListUpBound function shown in Listing 21.2, which returns the maximum element of the passed list:

    def maxListUpBound[T <: Ordered[T]](elements: List[T]): T = 
      elements match {
        case List() =>
          throw new IllegalArgumentException("empty list!")
        case List(x) => x
        case x :: rest =>
          val maxRest = maxListUpBound(rest)
          if (x > maxRest) x
          else maxRest
      }
Listing 21.2 - A function with an upper bound.

The signature of maxListUpBound is similar to that of orderedMergeSort, shown in Listing 19.12 here: it takes a List[T] as its argument, and specifies via an upper bound that T must be a subtype of Ordered[T]. As mentioned at the end of Section 19.8, one weakness with this approach is that you can't use the function with lists whose element type isn't already a subtype of Ordered. For example, you couldn't use the maxListUpBound function to find the maximum of a list of integers, because class Int is not a subtype of Ordered[Int].

Another, more general way to organize maxListUpBound would be to require a separate, second argument, in addition to the List[T] argument: a function that converts a T to an Ordered[T]. This approach is shown in Listing 21.3. In this example, the second argument, orderer, is placed in a separate argument list and marked implicit.

    def maxListImpParm[T](elements: List[T])
          (implicit orderer: T => Ordered[T]): T =
  
    elements match {       case List() =>          throw new IllegalArgumentException("empty list!")       case List(x) => x       case x :: rest =>         val maxRest = maxListImpParm(rest)(orderer)         if (orderer(x) > maxRest) x         else maxRest     }
Listing 21.3 - A function with an implicit parameter.

The orderer parameter in this example is used to describe the ordering of Ts. In the body of maxListImpParm, this ordering is used in two places: a recursive call to maxListImpParm, and an if expression that checks whether the head of the list is larger than the maximum element of the rest of the list.

The maxListImpParm function, shown in Listing 21.3, is an example of an implicit parameter used to provide more information about a type mentioned explicitly in an earlier parameter list. To be specific, the implicit parameter orderer, of type T => Ordered[T], provides more information about type T—in this case, how to order Ts. Type T is mentioned in List[T], the type of parameter elements, which appears in the earlier parameter list. Because elements must always be provided explicitly in any invocation of maxListImpParm, the compiler will know T at compile time, and can therefore determine whether an implicit definition of type T => Ordered[T] is in scope. If so, it can pass in the second parameter list, orderer, implicitly.

This pattern is so common that the standard Scala library provides implicit "orderer" methods for many common types. You could therefore use this maxListImpParm method with a variety of types:

  scala> maxListImpParm(List(1,5,10,3))
  res10: Int = 10
  
scala> maxListImpParm(List(1.55.210.73.14159)) res11: Double = 10.7
scala> maxListImpParm(List("one""two""three")) res12: java.lang.String = two
In the first case, the compiler inserted an orderer function for Ints; in the second case, for Doubles; in the third case, for Strings.

A style rule for implicit parameters As a style rule, it is best to use a custom named type in the types of implicit parameters. For example, the types of prompt and drink in the previous example was not String, but PreferredPrompt and PreferredDrink, respectively. As a counterexample, consider that the maxListImpParm function could just as well have been written with the following type signature:

  def maxListPoorStyle[T](elments: List[T])
        (implicit orderer: (T, T) => Boolean): T
To use this version of the function, though, the caller would have to supply an orderer parameter of type (T, T) => Boolean. This is a fairly generic type that includes any function from two Ts to a Boolean. It does not indicate anything at all about what the type is for; it could be an equality test, a less-than test, a greater-than test, or something else entirely.

The actual code for maxListImpParm, given in Listing 21.3, shows better style. It uses an orderer parameter of type T => Ordered[T]. The word Ordered in this type indicates exactly what the implicit parameter is used for: it is for ordering elements of T. Because this orderer type is more explicit, it becomes no trouble to add implicit conversions for this type in the standard library. To contrast, imagine the chaos that would ensue if you added an implicit of type (T, T) => Boolean in the standard library, and the compiler started sprinkling it around in people's code. You would end up with code that compiles and runs, but that does fairly arbitrary tests against pairs of items!

Thus the style rule: use at least one role-determining name within the type of an implicit parameter.

21.6 View bounds [link]

The previous example had an opportunity to use an implicit but did not. Note that when you use implicit on a parameter, then not only will the compiler try to supply that parameter with an implicit value, but the compiler will also use that parameter as an available implicit in the body of the method! Thus, both uses of orderer within the body of the method can be left out.

    def maxList[T](elements: List[T])
          (implicit orderer: T => Ordered[T]): T =
  
    elements match {       case List() =>          throw new IllegalArgumentException("empty list!")       case List(x) => x       case x :: rest =>         val maxRest = maxList(rest)  // (orderer) is implicit         if (x > maxRest) x           // orderer(x) is implicit         else maxRest     }
Listing 21.4 - A function that uses an implicit parameter internally.

When the compiler examines the code in Listing 21.4, it will see that the types do not match up. For example, x of type T does not have a > method, and so x > maxRest does not work. The compiler will not immediately stop, however. It will first look for implicit conversions to repair the code. In this case, it will notice that orderer is available, so it can convert the code to orderer(x) > maxRest. Likewise for the expression maxList(rest), which can be converted to maxList(rest)(ordered). After these two insertions of implicits, the method fully type checks.

Look closely at maxList. There is not a single mention of the ordered parameter in the text of the method. All uses of ordered are implicit. Surprisingly, this coding pattern is actually fairly common. The implicit parameter is used only for conversions, and so it can itself be used implicitly.

Now, because the parameter name is never used explicitly, the name could have been anything. For example, maxList would behave identically if you left its body alone but changed the parameter name:

  def maxList[T](elements: List[T])
        (implicit converter: T => Ordered[T]): T =
    // same body...
For that matter, it could just as well be:
  def maxList[T](elements: List[T])
        (implicit iceCream: T => Ordered[T]): T =
    // same body...
Because this pattern is common, Scala lets you leave out the name of this parameter and shorten the method header by using a view bound. Using a view bound, you would write the signature of maxList as shown in Listing 21.5.

    def maxList[T <% Ordered[T]](elements: List[T]): T =
      elements match {
        case List() => 
          throw new IllegalArgumentException("empty list!")
        case List(x) => x
        case x :: rest =>
          val maxRest = maxList(rest)  // (orderer) is implicit
          if (x > maxRest) x           // orderer(x) is implicit
          else maxRest
      }
Listing 21.5 - A function with a view bound.

You can think of "T <% Ordered[T]" as saying, "I can use any T, so long as T can be treated as an Ordered[T]." This is different from saying that T is an Ordered[T], which is what an upper bound, "T <: Ordered[T]", would say. For example, even though class Int is not a subtype of Ordered[Int], you could still pass a List[Int] to maxList so long as an implicit conversion from Int to Ordered[Int] is available. Moreover, if type T happens to already be an Ordered[T], you can still pass a List[T] to maxList. The compiler will use an implicit identity function, declared in Predef:

  implicit def identity[A](x: A): A = x
In this case, the conversion is a no-op; it simply returns the object it is given.

View bounds and upper bounds

The maxListUpBound function, of Listing 21.2, specifies that T is an Ordered[T] with its upper bound, T <: Ordered[T]. By contrast, the maxList function, of Listing 21.5, specifies that T can be treated as an Ordered[T] with its view bound, T <% Ordered[T]. If you compare the code of maxListUpBound with that of maxList, you'll find that the only non-cosmetic difference between the two is that the upper bound symbol, <:, is changed to a view bound symbol, <%. But maxList of Listing 21.5 can work with many more types.

    object Mocha extends Application {
  
    class PreferredDrink(val preference: String)
    implicit val pref = new PreferredDrink("mocha")
    def enjoy(name: String)(implicit drink: PreferredDrink) {       print("Welcome, "+ name)       print(". Enjoy a ")       print(drink.preference)       println("!")     }
    enjoy("reader")   }
Listing 21.6 - Sample code that uses an implicit parameter.

  $ scalac -Xprint:typer mocha.scala
  [[syntax trees at end of typer]]
// Scala source: mocha.scala
  package <empty> {
    final object Mocha extends java.lang.Object with Application
        with ScalaObject {
  
     // ...
    private[this] val pref: Mocha.PreferredDrink =       new Mocha.this.PreferredDrink("mocha");     implicit <stable> <accessor>       def pref: Mocha.PreferredDrink = Mocha.this.pref;     def enjoy(name: String)         (implicit drink: Mocha.PreferredDrink): Unit = {       scala.this.Predef.print("Welcome, ".+(name));       scala.this.Predef.print(". Enjoy a ");       scala.this.Predef.print(drink.preference);       scala.this.Predef.println("!")     };     Mocha.this.enjoy("reader")(Mocha.this.pref)   } }
Listing 21.7 - Sample code after type checking and insertion of implicits.

21.7 Debugging implicits [link]

Implicits are an extremely powerful feature in Scala, but one which is sometimes difficult to get right and to debug. This section contains a few tips for debugging implicits.

Sometimes you might wonder why the compiler did not find an implicit conversion that you think should apply. In that case it helps to write the conversion out explicitly. If that also gives an error message, you then know why the compiler could not apply your implicit. For instance, assume that you mistakenly took stringWrapper to be a conversion from Strings to Lists, instead of RandomAccessSeqs. You would wonder why the following does not work:

  scala> val chars: List[Char] = "xyz"
  <console>:12: error: type mismatch;
   found   : java.lang.String("xyz")
   required: List[Char]
         val chars: List[Char] = "xyz"
                                 ^

In that case it helps to write the stringWrapper conversion explicitly, to find out what went wrong:

  scala> val chars: List[Char] = stringWrapper("xyz")
  <console>:12: error: type mismatch;
   found   : java.lang.Object with RandomAccessSeq[Char]
   required: List[Char]
         val chars: List[Char] = stringWrapper("xyz")
                                 ^

With this, you have found the cause of the error: stringWrapper has the wrong return type. On the other hand, it's also possible that inserting the conversion explicitly will make the error go away. In that case you know that one of the other rules (such as the Scope Rule) was preventing the implicit from being applied.

When you are debugging a program, it can sometimes help to see what implicit conversions the compiler is inserting. The -Xprint:typer option to the compiler is useful for this. If you run scalac with this option, then the compiler will show you what your code looks like after all implicit conversions have been added by the type checker. An example is shown in Listing 21.6 and Listing 21.7. If you look at the last statement in each of these listings, you'll see that the second parameter list to enjoy, which was left off in the code in Listing 21.6:

  enjoy("reader")
was inserted by the compiler, as shown in Listing 21.7:
  Mocha.this.enjoy("reader")(Mocha.this.pref)

If you are brave, try scala -Xprint:typer to get an interactive shell that prints out the post-typing source code it uses internally. If you do so, be prepared to see an enormous amount of boilerplate surrounding the meat of your code.

21.8 Conclusion [link]

Implicits are a powerful, code-condensing feature of Scala. This chapter has shown you Scala's rules about implicits, and it has shown you several common programming situations where you can profit from using implicits.

As a word of warning, implicits can make code confusing if they are used too frequently. Thus, before adding a new implicit conversion, first ask whether you can achieve a similar effect through other means, such as inheritance, mixin composition, or method overloading. If all of these fail, however, and you feel like a lot of your code is still tedious and redundant, then implicits might just be able to help you out.


Footnotes for Chapter 21:

[1] In fact, the Predef object already defines a stringWrapper conversion with similar functionality, so in practice you can use this conversion instead of defining your own.

[2] Variables and singleton objects marked implicit can be used as implicit parameters. This use case will be described later in this chapter.

[3] The Scala compiler backend will treat the conversion specially, however, translating it to a special "i2d" bytecode. So the compiled image is the same as in Java.


Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use