Using Implicit Conversions

Let’s create a practical example of implicit conversions using string interpolators. Scala has some nice built-in string interpolators—see String Interpolation—but you can also create your own custom interpolators quite easily by using the concepts we’ve seen in this chapter. We’ll create a custom interpolator that will mask out select values from the processed string. To show that the interpolation can return something other than String—just about any object—we’ll return a StringBuilder as the result of processing the string. But first let’s revisit string interpolation to explore some concepts we need for the implementation.

When Scala sees the form

 
interpolatorName"text1 $expr1 text2 $expr2"

the compiler turns that into a function call on a special class named StringContext after separating the texts and the expressions. In effect, it translates the example form to

 
new StringContext("text1", "text2", "").interpolatorName(expr1, expr2)

The separated-out texts are sent as arguments to the constructor of StringContext and are available through its parts property. The expressions, on the other hand, are passed as arguments to the StringContext’s method with the name of the interpolator. In the arguments sent to the constructor, each argument is a piece of text before an expression, with the last argument representing text that follows the last expression. In this example, it’s empty ("") since there’s no text after the last expression.

As we saw earlier, the three functions that StringContext already provides are s, f, and raw. We’ll create a new interpolator named mask that will only partially display select expressions in the processed string. Let’s first use the interpolator as if it were a built-in function. Once we get a grasp of the intent from an example use, we’ll then create the interpolator.

MakingUseOfTypes/mask.scala
 
object​ UseInterpolator ​extends​ App {
 
import​ MyInterpolator._
 
 
val​ ssn = ​"123-45-6789"
 
val​ account = ​"0246781263"
 
val​ balance = 20145.23
 
 
println(mask​"""Account: $account
 
|Social Security Number: $ssn
 
|Balance: $$^${balance}
 
|Thanks for your business."""​.stripMargin)
 
}

The call to println receives a string with the yet-to-be-written mask interpolation. The string attached to mask is a heredoc (see Strings and Multiline Raw Strings) with texts and expressions. Our mask interpolator will return a StringBuilder with each expression’s string representation converted, where all but the last four characters are replaced with “…,” unless the expression is preceded with a caret (^). In the example, we’re asking the mask function to keep the value of balance intact by placing a caret in front of it, but the display of the other two expressions, account and ssn, will be altered.

To process this string Scala will transform the code with mask to

 
new StringContext("Account:", "Social...", ...).mask(account, ssn, balance)

However, there is no mask method in StringContext. That should not deter us; there’s no days function in integer but we made the code 2 days ago work. We’ll apply the same trick of implicit conversion here.

Since the compiler is looking to call a method on the StringContext, our implicit value class should take an instance of StringContext as its constructor parameter and implement the mask method—it’s that simple. Let’s get to the code.

MakingUseOfTypes/MyInterpolator.scala
 
object​ MyInterpolator {
 
implicit​ ​class​ Interpolator(​val​ context: StringContext) ​extends​ ​AnyVal​ {
 
def​ mask(args: ​Any​*) = {
 
val​ processed = context.parts.zip(args).map { item =>
 
val​ (text, expression) = item
 
if​(text.endsWith(​"^"​))
 
s​"${text.split('^')(0)}$expression"
 
else
 
s​"$text...${expression.toString takeRight 4}"
 
}.mkString
 
 
new​ StringBuilder(processed).append(context.parts.last)
 
}
 
}
 
}

The implicit value class Interpolator is housed in the singleton MyInterpolator. It takes an instance of StringContext as the constructor parameter, and extends AnyVal.

As its core behavior, the mask method combines the expressions given as parameters and the texts available in the StringContext’s parts. We can easily combine these two collections into one, much like the way we can combine the two sides of a winter jacket—using the zip function. This function works with two arrays—the texts and the expressions in this example—and produces one array of tuples. Each element in the tuple has one text and the expression that follows the text. One excess text that follows the last expression is discarded by the zip function—we’ll take care of this in the last step. We use the map method that we saw in Chapter 1, Exploring Scala to transform from this array of tuples to an array of combined strings. As we iterate through each text-expression pair, if a text ends with a caret, we leave the expression intact. Otherwise, we replace it with the ellipsis and last four characters using the takeRight method. Finally we combine the array of strings with the mkString method and append the final text that follows the last expression to the result StringBuilder.

Let’s compile and run the code using the following commands:

 
scalac MyInterpolator.scala mask.scala
 
scala UseInterpolator

The output of our interpolator, in full glory, is shown next:

 
Account: ...1263
 
Social Security Number: ...6789
 
Balance: $20145.23
 
Thanks for your business.

This is a little example, but it brought together a number of nice little concepts. We used singletons, implicit value classes, the map function with functional style to iterate and process the elements, a custom string interpolator, and more.

Take some time to play with the code, evolve it, introduce some more formatting, and implement it, while keeping the implementation close to the functional style.

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

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