Why Scala Implicits
There's a huge debate on Scala Implicits since they've introduced it to the language. Some people love it while some people comment it on making the code unpredictable. So should we use it?
Before I tell you what implicits
actually are, I'll tell you what they
solve.
If you have to store the details of a Person's name and age, there are two ways to do it.
class Person(name: String, age: Int)
While this does the work we need, it's not the right way to do it. This simple declaration lacks a lot of features.
To get most of all the features(mainly pattern matching) we have to
declare a case class
. So, Now the above code will be turned to:
case class Person(name: String, age: Int)
Usage:
Person("Gnanesh Kunal", 20)
The name here is just a simple string.
It doesn't say anything about the person's firstname
and lastname
.
We simply have to split the given string and must think the first item
in the array corresponds to his first name. But that's still not the
right way to do it. But for now, let me keep it simple and limit name to
just a string.
It would be better if we have a separate datatype that defines something. So, let's define some.
case class Name(name: String) case class Age(age: Int)
So now, let me use those datatypes.
val name = Name("Gnanesh Kunal") val age = Age(20) val person = Person(name, age)
If you try running the above code, you would get a type mismatch error.
scala> val person = Person(name, age) <console>:33: error: type mismatch; found : Name required: String val person = Person(name, age) ^ <console>:33: error: type mismatch; found : Age required: Int val person = Person(name, age) ^
What we have known here is that the name
and age
variable have
Name
and Age
datatypes respectively. But what we need is a String
and Int
. As I've told you earlier that using custom datatypes leads us
to define many different data structures.
The Person class must be re-implemented using the newer data types.
case class Person(name: Name, age: Age)
This works now.
scala> Person(name, age) res1: Person = Person(Name(Gnanesh Kunal),Age(20))
But do you think the following code is gonna work?
Person("Gnanesh Kunal", 20)
scala> Person("Gnanesh Kunal", 20) <console>:30: error: type mismatch; found : String("Gnanesh Kunal") required: Name Person("Gnanesh Kunal", 20) ^ <console>:30: error: type mismatch; found : Int(20) required: Age Person("Gnanesh Kunal", 20)
The same error again. The object must be initialized like:
Person(Name("Gnanesh Kunal"), Age(20))
Another implementation would be to try multiple constructors.
case class Person(n: String, a: Int) { var name: Name = Name(n) var age: Age = Age(a) def this(n: Name, a: Age) = this(n.name, a.age) }
The parameters names have been changed to n
and a
since each
parameter defined will become a property in Scala.
Now let us try to create the objects.
scala> Person("Gnanesh Kunal", 20) # Yay!! res10: Person = Person(Gnanesh Kunal,20) scala> Person(Name("Gnanesh Kunal"), Age(20)) # Boo!! <console>:30: error: type mismatch; found : Name required: String Person(Name("Gnanesh Kunal"), Age(20)) ^ <console>:30: error: type mismatch; found : Age required: Int Person(Name("Gnanesh Kunal"), Age(20)) ^
Why does it still not work?
Well!! The this(n: Name, a: Age)
is an auxiliary constructor. The
apply
methods which will be generated for case class
objects won't
be defined for them. To define a class with Name
and Age
as
parameters types, we must use the new
keyword.
new Person(Name("Gnanesh Kunal"), Age(20))
To do something like Person(Name("Gnanesh Kunal"), Age(20))
to work we
must create a companion object.
// Person class Implementation ... object Person { def apply(n: Name, a: Int) = new Person(n.name, a.age) }
Finally it works.
scala> Person("Gnanesh Kunal", 20) res14: Person = Person(Gnanesh Kunal,20) scala> Person(Name("Gnanesh Kunal"), Age(20)) res15: Person = Person(Gnanesh Kunal,20)
The complete above code to make the person class work.
case class Name(name: String) case class Age(age: Int) case class Person(n: String, a: Int) { var name: Name = Name(n) var age: Age = Age(a) def this(n: Name, a: Age) = this(n.name, a.age) } object Person { def apply(n: Name, a: Int) = new Person(n.name, a.age) }
So much hassle to define a simple class, right?
Implicits to the rescue.
Implicits can be said as a syntactic sugar.
Doing everything defined above under a minute.
case class Name(name: String) case class Age(age: Int) case class Person(name: Name, age: Age) implicit def strToName(name: String): Name = Name(name) implicit def intToAge(age: Int): Age = Age(age)
Everything fits.
scala> Person(Name("Gnanesh Kunal"), Age(20)) res17: Person = Person(Name(Gnanesh Kunal),Age(20)) scala> Person("Gnanesh Kunal", 20) res18: Person = Person(Name(Gnanesh Kunal),Age(20))
The way Person("Gnanesh Kunal", 20)
works are, first, the compiler
will check the parameter types. The compiler expects the first parameter
of type Name
but finds the type String
it'll be ready to throw an
error. Before throwing an error, the compiler checks for some function
which converts the object of type String
to Name
. Something like
s: String => Name
.
So as we have defined a function which converts a String
datatype to
Name
it calls the function for us automatically. To make the compiler
to call the function we have to prefix the keyword implicit
. Implicit
functions can't we called explicitly.
cool ☜(⌒▽⌒)☞
implicit=s aren't just limited to functions, there's =implicit
variables, classes, objects etc.