The usage of implicit can be found every time we try to convert a string into an integer, or boolean or any other basic type:
1 2 3 | val myInt:Int = "3".toInt val myBool:Boolean = "true".toBoolean val myDouble:Double = "1.0".toDouble |
The interesting aspect of these methods are that they are "attached" to the type String, but in Java the String class is marked as final so cannot be extended.
How is it possible that in scala we have these new methods ?
Here is where the power of implicit comes in place. This is also called "dynamic polymorphism".
All the magic is made by the compiler.
How does it work ?
In scala you can define a method as implicit.
Lets make an example and consider we want to write a class to represent the rational numbers.
We can define that as a case class as below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | case class Rational(n:Int, d:Int) { require(d!=0) private val g = gcd(n.abs, d.abs) val num:Int = n/g val den:Int = d/g def +(other:Rational):Rational = Rational( this.num*other.den + this.den*other.num, this.den*other.den ) def *(other:Rational):Rational = Rational( this.num*other.num, this.den*other.den ) private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) } |
We can now use the rational numbers in the following way:
1 2 3 4 5 6 | object RationalApp extends App { val r1 = Rational(3, 4) val r2 = Rational(5, 4) val r3:Rational = r1 + r2 } |
The only limitation we have in our implementation is if we want to use Integer numbers together with our rationals, we will have a compilation error if we try to do the following:
1 2 3 | val r1 = Rational(3, 4) val r2 = r1 + 1 //(1) compilation error: method Rational.+(Int) is not defined val r3 = 1 + r1 //(2) compilation error: method Int.+(Rational) is not defined |
As commented in the code above:
- In this case the class Rational doesn't have an overloaded definition for method + which accepts as argument an Int. This can be fixed by simply overloading the + method.
- This case is more complex as the method + belongs to the type Int which in Java is final so we cannot extend and eventually overload with a Rational argument.
In order to solve this problem we need to use a little trick.
Lets first write the code to solve the problem:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package object converters { implicit def intToRational(v:Int):Rational = Rational(v, 1) } ... import converters._ object RationalApp extends App { val r1 = Rational(3,4) val r2 = Rational(5, 4) val r3:Rational = r1 + r2 val r4 = r1 + 1 val r5 = 1 + r1 } |
All works fine and r4 and r5 are Rational values.
What did happen ?
We defined a new method "implicit" inside the package converters, the method with the signature
Int => Rational
Then before our main class we have imported everything from the package converters.
When the compiler had to evaluate the expression:
val r4 = r1 + 1
it initially failed because there is no method + for rational which accepts an Int, however there is a method implicitly defined which is able to convert an Int into a Rational, so the compiler will convert the Int into Rational and then the method + is defined for rationals and everything works as expected.
What is more interesting is the next expression:
val r5 = 1 + r1
In this case the compiler will initially fail because there is no method + defined for Int which accepts a Rational, however there is an implicit method imported that can convert the Int => Rational, once the Int is converted into Rational then all works because Rational has a method + which accepts another Rational as argument.
So the 2 expressions above will be translated by the compiler in the followings:
val r4 = r1.+(intToRational(1)) val r5 = intToRational(1).+(r1)
The implicit mechanism is a powerful tool especially when it is used to implement the Typeclass concept, a mechanism used in Haskell programming model which I will exploit into another post.
No comments:
Post a Comment