Implicit Type Conversions

The compile-time type checking is quite rigorous in Scala. If you don’t pass the type expected by a function or invoke an arbitrary method on an instance, you’ll get a stern error at compile time. While this is good most of the time, occasionally you’d want to pass an instance of one type where another type is expected—mainly where such use will make code intuitive, more expressive, and easy to write. Likewise, you may want to invoke your own domain-specific convenient methods on third-party classes. This can give an illusion of greater power to open and add methods to existing third-party classes. All this is possible in Scala with just a few tricks by using type conversions.

There are two different ways to implement type conversions—writing implicit functions and creating implicit classes. The first approach has been around for a while in Scala, but the implicit classes are relatively new. Let’s explore both.

Implicit Functions

By default, at the sight of an invalid method call, the Scala compiler gets ready to snicker. However, if it sees an implicit conversion in the current scope, it quietly transforms your object using that conversion and applies the method you ask for. Let’s see how with a domain-specific language (DSL)—a fluent syntax—we’ll implement such an example.

When working with date and time operations it’d be quite convenient and more readable to write code like this:

 
2 days ago
 
5 days from_now

That looks more like data input than code—one of the characteristics of DSLs. At the first glance, we took advantage of Scala’s optional treatment of dot and parentheses. But there’s more—we’re calling a method days on 2 and sending in a variable ago in the first expression. In the second expression, we’re calling the method on 5 and sending in a variable from_now. Since Int does not have that method, it would appear like magic if that code works.

If we try to compile the previous code, Scala will complain that days is not a method on Int. But, relentless programmers don’t easily take no for an answer; we’ll get it working. We can ask Scala to quietly convert the Int to something that will help accomplish the previous operation.

With implicit type conversion you can extend the language to create your own vocabulary that’s specific to your application/domain or to create your own domain-specific languages.

Let’s start with some crufty code to first understand the concept and then refactor that into a nice class.

We need to define the variables ago and from_now and ask Scala to accept the days method. Defining variables is easy, but for it to accept the method, let’s create a class DateHelper that takes an Int as a constructor parameter:

 
import​ scala.language.implicitConversions
 
import​ java.time.LocalDate
 
 
class​ DateHelper(offset: ​Int​) {
 
def​ days(when: ​String​) = {
 
val​ today = LocalDate.now
 
when ​match​ {
 
case​ ​"ago"​ => today.minusDays(offset)
 
case​ ​"from_now"​ => today.plusDays(offset)
 
case​ _ => today
 
}
 
}
 
}

If you want to use implicit conversion functions, Scala will require import scala.language.implicitConversions. This readily warns the reader that type conversions are ahead in code.

The DateHelper class provides the days method we want. The match method used in that method is part of Scala’s pattern matching facility—we’ll look at that in Chapter 9, Pattern Matching and Regular Expressions. Since DateHelper is a regular class we can create an instance and invoke the days method. But, the real fun is in calling the days method on an Int and letting Scala quietly convert the Int to an instance of DateHelper so the method can be called. The Scala feature that enables this trick comes in the form of a simple function prefixed with the implicit keyword.

If a function is marked as implicit Scala will automatically pick it up if it’s present in the current scope, visible through current imports, or present in the current file. Let’s create the implicit function and make our fluent calls:

 
implicit​ ​def​ convertInt2DateHelper(offset: ​Int​) = ​new​ DateHelper(offset)
 
 
val​ ago = ​"ago"
 
val​ from_now = ​"from_now"
 
 
val​ past = 2 days ago
 
val​ appointment = 5 days from_now
 
 
println(past)
 
println(appointment)

If you run the code along with the definition of DateHelper, you’ll see that Scala automatically converts the given numbers into an instance of DateHelper and invokes the days method to produce this result:

 
2015-08-11
 
2015-08-18

Now that the code works, it’s time to clean it up a bit. We don’t want to write the implicit converter each time we need the conversion. By tucking away the converter into a separate singleton object, we get better reusability and ease of use. Let’s move the converter to the companion object of DateHelper:

MakingUseOfTypes/DateHelper.scala
 
import​ scala.language.implicitConversions
 
import​ java.time.LocalDate
 
 
class​ DateHelper(offset: ​Int​) {
 
def​ days(when: ​String​) = {
 
val​ today = LocalDate.now
 
when ​match​ {
 
case​ ​"ago"​ => today.minusDays(offset)
 
case​ ​"from_now"​ => today.plusDays(offset)
 
case​ _ => today
 
}
 
}
 
}
 
 
object​ DateHelper {
 
val​ ago = ​"ago"
 
val​ from_now = ​"from_now"
 
implicit​ ​def​ convertInt2DateHelper(offset: ​Int​) = ​new​ DateHelper(offset)
 
}

When we import DateHelper, Scala will automatically find the converter for us. This is because Scala applies conversions in the current scope and in the scope of what we import.

Here’s an example of using the implicit conversion we wrote in DateHelper:

MakingUseOfTypes/DaysDSL.scala
 
import​ DateHelper._
 
 
object​ DaysDSL ​extends​ App {
 
val​ past = 2 days ago
 
val​ appointment = 5 days from_now
 
 
println(past)
 
println(appointment)
 
}

Here’s the result of compiling and running the code:

 
2015-08-11
 
2015-08-18

Scala has a number of implicit conversions already defined in the Predef object and the package object of the scala package, which are imported by default. For example, when we write 1 to 3, Scala implicitly converts 1 from Int to the rich wrapper RichInt and invokes its to method.

If multiple conversions are visible, Scala will pick the one that’s appropriate so the code compiles. However, Scala applies at most one implicit conversion at a time. If multiple conversions compete at the same method call, Scala will give an error.

The magic of calling 2 days ago is nice, but we had to write one class and one conversion function. What name should we give to that function, where do we put it, and how do programmers find it? We can get rid of the conversion function, and all those concerns, with implicit classes. You write an implicit class and Scala takes care of creating the necessary plumbing, as you’ll see next.

Implicit Classes

Instead of creating a regular class and a separate implicit conversion method, you can tell Scala that the sole purpose of a class is to serve as an adapter or converter. To do this, mark a class as an implicit class. When using an implicit class Scala places a few restrictions. Most notably it can’t be a stand-alone class—it has to be within a singleton object, a class, or a trait. Let’s rework the fluent date example to use an implicit class.

MakingUseOfTypes/DateUtil.scala
 
object​ DateUtil {
 
val​ ago = ​"ago"
 
val​ from_now = ​"from_now"
 
 
implicit​ ​class​ DateHelper(​val​ offset: ​Int​) {
 
def​ days(when: ​String​) = {
 
import​ java.time.LocalDate
 
val​ today = LocalDate.now
 
when ​match​ {
 
case​ ​"ago"​ => today.minusDays(offset)
 
case​ ​"from_now"​ => today.plusDays(offset)
 
case​ _ => today
 
}
 
}
 
}
 
}

The singleton object DateUtil serves as a placeholder for the variables or constants and the modified class DateHelper, now declared implicit, in its new home. We also moved the import of LocalDate to within the class where it’s needed. Scala does not require the implicitConversions import when implicit classes are used. This is because, unlike arbitrary conversion functions that can be pulled in, the implicit classes are more visible from the declaration and the import of their scope.

Using the implicit conversion is not much different from the previous example. Let’s use the modified conversion:

MakingUseOfTypes/DateUtil.scala
 
object​ UseDateUtil ​extends​ App {
 
import​ DateUtil._
 
 
val​ past = 2 days ago
 
val​ appointment = 5 days from_now
 
 
println(past)
 
println(appointment)
 
}

The definition of ago and from_now came from the singleton object DateUtil, as did the implicit conversion through the DateHelper that is also part of this housing object.

For providing fluency, ease of use, and extending existing classes with domain-specific methods, prefer implicit classes over implicit methods—implicit classes express the intention more clearly and also are easier to locate than arbitrary conversion methods. They can also remove object creation overhead when combined with value classes, which you’ll see next.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.19.17