In this section we will look at the two types of implicits in scala, and also deep dive into how implicit parameters works internally.
Types of Implicits
Implicits in Scala refers to Implicits in Scala refers to either of the below
- a value that can be passed “automatically”
- a conversion from one type to another that is made automatically.
Implicit Conversions
If we calls a method m on an object o of a class C, and that class does not support method m, then Scala will look for an implicit conversion from C to something that does support m.
A simple example would be the method map on String:
"abc".map(_.toInt)
String does not support the method map, but StringOps does, and there’s an implicit conversion from String to StringOps available in Predef
@inline implicit def augmentString(x: String): StringOps = new StringOps(x)
Implicit Parameters
These parameters are passed to method calls like any other parameter, but the compiler tries to fill them in automatically. We can pass these parameters explicitly also.
Consider the example below
def test(arg: Int)(implicit defaultValue: Int) = arg + defaultValue
implicit val i: Int = 10 test(2)(10) //12 test(2) //12 test(4) //14
Following are illegal types of implicit declaration
def testOne(implicit arg: String, implicit dummy: Int) = println(arg + ", " + dummy) //Doesn't compiles unless we remove the implicit from second argument. def testTwo(arg: String, implicit dummy: Int) = println(arg + ", " + dummy) //This too also doesn't compiles.
To make above work, we can either make only the first argument implicit. e.g
def testTwo(implicit arg: String, dummy: Int) = println(arg + ", " + dummy)
Of if we want that the second argument ‘dummy’, needs to be implicit then we can use function currying form to define the function. e.g
def testTwo(arg: String)(implicit dummy: Int) = println(arg + ", " + dummy)
The below also however does not complies (when we add more than one implicit to curried function).
def testTwo(implicit arg: String)(implicit dummy: Int) = println(arg + ", " + dummy)
Few more examples
def testTwo(implicit arg: String, dummy: Int) = println(arg + ", " + dummy) implicit val str: String = "John Doe" implicit val intVal: Int = 35 def t2: Int => Unit = testTwo("Susan", _:Int) def t3: String => Unit = testTwo(_:String, 40) t2 // Returns, Int => Unit only // As the integer arg in this new generated function is not implicit t3 // Returns, String => Unit only // As the String arg in this new generated function is not implicit testTwo // prints "John Doe, 35"
All above examples lead us to following conclusions
- There can be only 1 implicit definition in a function parameters, and it has to be first parameter in the parameter list.
- Adding implicit to first parameter makes all following parameters as implicit.
- Implicit parameters don’t get carried forward to partially applied functions
Implicit conversions as implicit parameters
There’s one situation where an implicit is both an implicit conversion and an implicit parameter. e.g
def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)
In above example, method getIndex can receive any object, as long as there is an implicit conversion available from its class to Seq[T]. Because of this reason, a String can be passed to getIndex, and it will work. e.g
getIndex("abc", 'a')
Internally, the compiler changes seq.IndexOf(value) to conv(seq).indexOf(value).
Resolving Implicits
Scala looks at following places to resolve the implicits.
1. Look in current Scope
1.1 Implicits defined in current scope
Consider following example
def add(x: Int)(implicit y: Int) = x + y
implicit val n: Int = 5 add(5) // takes n from the current scope, res: Int = 10
There can also be cases when it won’t work. e.g
implicit val n: String = "20" add(5) // No implicit value of type Integer found
implicit val n: Int = 5 implicit val z: Int = 10 add(5) // ambigous implicit types found
implicit val n: Int = 5 implicit val z: Int = 10 add(5) // ambigous implicit types found
implicit val n: Int = 5 implicit val z: Int = 10 add(5) // ambiguous implicit types found
1.2 Explicit imports
import scala.collection.JavaConversions.mapAsScalaMap def env = System.getenv() // Java map val term = env("TERM") // implicit conversion from Java Map to Scala Map
1.3 Wildcard imports
def sum[T : Integral](list: List[T]): T = { val integral = implicitly[Integral[T]] import integral._ // get the implicits in question into scope list.foldLeft(integral.zero)(_ + _) }
2. Look at associated types in
2.1 Companion objects of a type
The object companion of the “source” type is looked into. Example
for { x <- List(1, 2, 3) y <- Some('x') } yield (x, y)
That expression is translated by the compiler into
List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))
However, List.flatMap expects a TraversableOnce, which Option is not. The compiler then looks inside Option’s object companion and finds the implicit conversion to Iterable, which is a TraversableOnce, making this expression correct.
2.2 Companion object of expected type
List(1, 2, 3).sorted
The method sorted takes an implicit Ordering.
def sorted[B >: A](implicit ord: Ordering[B]): Repr = { ... }
In this case, it looks inside the object Ordering, companion to the class Ordering, and finds an implicit Ordering[Int] there.
2.3 Companion object of the superclasses of expected type
class A(val n: Int) object A { implicit def str(a: A):String = "A: %d" format a.n } class B(val x: Int, y: Int) extends A(y) val b = new B(5, 2) val s: String = b // s == "A: 2" // gets the implicit conversion from Type of A to Type of String from the companion object of A
3. Implicit scope of an argument’s type
If you have a method with an argument type A, then the implicit scope of type A will also be considered. This available only since Scala 2.9.1.
“implicit scope” means all these rules will be applied recursively.
class A(val n: Int) {
def +(other: A) = new A(n + other.n)
override def toString: String = s"A : $n"
}
object A {
implicit def fromInt(n: Int) = new A(n)
}
// This becomes possible:
5 + new A(2) // Evaluates to class A with value 7
// because above expression is converted into this:
A.fromInt(5) + new A(2)
In above example, the companion object of A will be searched for implicits.
4. Implicit scope of type arguments
This is available since Scala 2.8.0. This is required to make the type class pattern really work.
e.g. Consider Ordering, for instance. With this we can make an Ordering for our own class automatically found.
class A(val n: Int) {
def +(other: A) = new A(n + other.n)
override def toString: String = s"A : $n"
}
object A {
implicit val ord = new Ordering[A] {
def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(x.n, y.n)
}
}
List(new A(5), new A(2)).sorted
//res0: List[A] = List(A : 2, A : 5)
As we saw, the method sorted expects an Ordering[A] (actually, it expects an Ordering[B], where B >: A). There isn’t any such thing inside Ordering, and there is no “source” type on which to look. It finds it inside A, which is a type argument of Ordering.
References
https://docs.scala-lang.org/tour/implicit-parameters.html
https://docs.scala-lang.org/tutorials/FAQ/finding-implicits.html
https://www.coursera.org/learn/progfun1/home/welcome
http://daily-scala.blogspot.in/2010/04/implicit-parameters.html