Implicit conversion and parameters in Scala
Implicit definitions are those in which compiler is allowed to insert into a program if there are any type errors. In other terms it is a final way to avoid some kind of errors in the code and continue with program execution. Consider the example a+b what happens if a and b are not compatible, of course the compiler will throw an error. But it will be very nice if the compiler tries whether there is any way that a can be converted into a type so that there won’t be any type mismatch, for example change a+b into convert(a) + b.
This is exactly what an implicit does, it checks whether there is any way to convert the type, so that a possible error can be avoided. Compiler follows a set of rules for applying implicit conversion.
Rules for implicit conversion
- Only definitions marked implicit are available
In the above example in order to apply the method convert(a), there must be such a method defined with a keyword implicit.
implicit def intToString(x: Int) = x.toString
If there is method marked as implicit in the given context of the code the compiler will automatically pick it up, and then conversion is performed.
- A given implicit must be in the given scope
Scala compiler will only consider the implicit conversion in the given scope, In the above example to apply the convert method you must bring it into the program scope. In order to use the implicit methods in a library we have to explicitly import it first. For example
import scala.preamble._
After importing the package scala.preamble we can use all the implicits defined inside the package. In a way we are bringing the implicits defined, into the current context of execution.
- One at a time
If the compiler is trying to resolve one implicit it is impossible to bring another one into context of execution.
convert1(convert2(x)) + y
Such an implicit conversion is not possible because conver2(x) is in progress.
- Whenever code type checks as it is written, no implicits are attempted
If the code is already working fine the compiler will not try to change it, this rule can also be taken as we can always covert an implicit with an explicit ones.
There are three methods where implicits are used inside a program they are conversions to an expected type,Conversions of the receiver of selection and implicit parameters now let us check each of these one by one.
1- Conversion to an expected type
If compiler sees type X but it needs to be converted into type Y, then compiler will check for any implicit function, if no such function is available it will throw an error.
scala> val k:Int = 3.6
<console>:11: error: type mismatch;
found : Double(3.6)
required: Int
val k:Int = 3.6
Solution: Define an implicit function to convert double to Int.
scala> implicit def doubleToInt(x: Double) = x.toInt
warning: there was one feature warning; re-run with -feature for details
doubleToInt: (x: Double)Intscala> val k:Int = 3.6
k: Int = 3
In the above example we can see double type automatically castes into an Integer. This happened because there is an implicit function doubleToInt in the same context. So when we assigned a double type to an Integer
doubleToInt(3.6) is applied and a value of 3 is obtained.
2- Converting the receiver
Implicit type conversion can be applied to the receiver of a method call.
Simulating new syntax
Map(1 -> "one", 2 -> "two", 3 -> "three")
In the above given example have you ever wondered how the operator -> is supported!!! -> is not an operator it is a function defined inside the ArrowAssoc class.
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)
...
}
When you write 1 -> “One” compiler will automatically convert 1 to ArrowAssoc so that -> method can be found and it can be used.
Implicit classes
An implicit class is simply a class that is declared with an Implicit keyword.
scala> case class Rectangle(width: Int, height: Int)
defined class Rectanglescala> implicit class RectangleMaker(width: Int) {
| def x(height: Int) = Rectangle(width, height)
|}
defined class RectangleMakerscala> val myRectangle = 3 x 4
myRectangle: Rectangle = Rectangle(3,4)
There exists no such operator such as x for int, but the compiler will look for an implicit conversion to Int, then it will find the implicit class RectangleMaker and a method x inside RectangleMaker.
3- Implicit parameters
We define a class PreferredPrompt, and an object Greeter with a method greet inside it.
scala> class PreferredPrompt(val preference: String)
defined class PreferredPromptscala> object Greeter {
| def greet(name: String)(implicit prompt: PreferredPrompt) = {
| println("Welcome, " + name + ". The system is ready.")
| println(prompt.preference)
| }
| }
defined object Greeter
Now we can provide prompt value explicitly as shown below
scala> val bobsPrompt = new PreferredPrompt("relax> ")
bobsPrompt: PreferredPrompt = PreferredPrompt@1218e12scala> Greeter.greet("Bob")(bobsPrompt)
Welcome, Bob. The system is ready.
relax>
If we need the compiler to provide the value for PreferredPrompt implicitly, we must define a variable of expected type that is of type PreferredPrompt as shown below.
object JoesPrefs {
implicit val prompt = new PreferredPrompt("Yes, master> ")
}
Then import to JoesPrefs bring it into the context of execution. Compiler will look for an implicit value within the current context of execution if found compiler will use it else it will throw an error.
scala> import JoesPrefs._
import JoesPrefs._scala> Greeter.greet("Joe")
Welcome, Joe. The system is ready.
Yes, master>
Debugging implicits
Implicits are very powerful features in scala, but sometimes it will be difficult to get it right. Now let us see few tips for debugging errors regarding implicits.
scala> val chars: List[Char] = "xyz"
<console>:17: error: type mismatch;
found : String("xyz")
required: List[Char]
val chars: List[Char] = "xyz"
Now in order to debug the error let us provide wrapString explicitly.
scala> val chars:List[Char] = wrapString("xyz")
<console>:17: error: type mismatch;
found : scala.collection.immutable.WrappedString
required: List[Char]
val chars:List[Char] =wrapString("xyz")
Now we got the error, return type of wrapString doesn't match with type of chars. If we doesn’t got any error after applying the function implicitly then we can infer a violation of scope rule occurred (No implicit method exists within the scope).