Customizing String Interpolation – An Example


OVERVIEW

This blog is continuation of – ‘An Invitation From Scala String Interpolation‘. Here we will explore how to define a custom string interpolator.

OBJECTIVE

In this blog the custom interpolator being designed works exactly like the ‘s interpolator’ with an extra ability to write the “post interpolation content” into a file and finally returning the status message whether the file write was successful or not.

STEPS TO SUCCESS

Step 1 “Starting Up” ->

Create new sbt project in intellij.

Step 2 “Including required dependencies and plugins” ->

a). libraryDependencies += “org.scalatest” %% “scalatest” % “2.2.4” % “test”

It includes the scalatest jar to project which facilitates code testing(add in build.sbt).

Also following plugins were used in the project (add in plugins.sbt)

a). addSbtPlugin(“org.scoverage” % “sbt-scoverage” % “1.3.5”)

Used to see the code coverage by unit tests

b). addSbtPlugin(“org.scalastyle” %% “scalastyle-sbt-plugin” % “0.8.0”)

this plugin checks if any scalastyle warning is present or not.

Step 3 “Creating Desired Directory Structure” ->

The following image shows the directory structure of the project :

blog6Pic

In ‘main’, the ‘scala’ sub-folder contains the implementation for our custom string interpolator and the ‘test’ sub-folder contains the test cases to test our implementation, the ‘resources’ folder contains the file ‘output’ in which the output is written.

Step 4 “Defining custom string interpolator” ->

Since all the String Interpolation methods are present in ‘StringContext’ class, our goal is to write code to add a new method to ‘StringContext’ (in this new method, we define what our custom interpolator does). We will achieve it using an implicit value class. Using an implicit class provides a more convenient syntax for defining extension methods(since we need not explicitly declare the class objects), while value classes do the job of adding new functionality to a class along with removing the run time overhead. A good example is the RichInt class in the standard library.
At compile time, the string literal of the form :

id”something1 $something2 something3″

is transformed into following call using compile time reflection :-

new StringContext(“something1 “,” something3″).id(something2)

‘something1 ‘ and ‘ something3’ are refered as ‘parts'(i.e. the number of parts a string breaks in while making a call to a string interpolator) and ‘something2’ is refered as ‘args'(argument to the string interpolation method) in the example below.
Now to make our own interpolator we create a class that adds a new method to ‘StringContext’ class

object StringInterpolation {

  implicit class WriteScore(val sc: StringContext) extends AnyVal {

    def write(args: Any*): String = {

      checkLengths(sc.parts.length, args.length) //'checkLengths' method checks that length of the parts = length of args + 1
      val strings = sc.parts.iterator
      val expressions = args.iterator
      val buf = new StringBuffer(treatEscapes(strings.next))
      while (strings.hasNext) { //the buffer contains the processed string along with the value of the variable references
        buf.append(expressions.next)
        buf.append(treatEscapes(strings.next))
      }
      val message: String = writeToFile(buf.toString) //'writeToFile' writes the interpolated string to a file
      message
    }

    ... // Few private methods, refer the git link below for full code.
  }
}

In above code the ‘WriteScore’ class is a value class since it extends from Anyval and it is declared as implicit so we do not need to explicitly instantiate the class to use its methods. ‘write’ method is our custom interpolator, it is similar to the ‘s interpolator’ i.e. it first process the string using ‘treatEscapes’ method of ‘StringContext’ object, then embed the variable expressions to it(by appending the string buffer) and finally write the post interpolation content to a file using ‘writeToFile’ method which returns the status whether write was successful or not.

Whenever we need this custom interpolator, we make following import :

import StringInterpolation.WriteScore

After the import, just use the interpolator kind of syntax to use the ‘write interpolator’. It is demonstrated in the test cases.

Step 5 “Testing” (The Climax) ->

Test cases implementing the ‘write interpolator’ are as follows :-

import org.scalatest.{Matchers, FlatSpec}
import StringInterpolation.WriteScore

class StringInterpolationSpec extends FlatSpec with Matchers {

  "StringInterpolation" should "run for 1 st case" in {

    val name: String = "Sahil"
    val views: Long = 564487
    val actual: String = write"Total view of $name on ${java.time.LocalDate.now} are = \t $views " //using our intorpolator method
    val expected: String = "File written successfully"
    actual should ===(expected)
  }

  .. // a few more test cases
}

We firstly import the implicit ‘WriteScore’ class and then use the ‘write interpolator’ just like the other interpolators provided by ‘StringContext’ class. One thing to note is that since we are processing the string here, ‘/t’ will be treated as a tab.’File written successfully’ is the status message returned when write to the file is successful. The content is written in file :

src -> test -> scala -> resources -> output
Full code is available on git :- https://github.com/knoldus/string-interpolation-example

Comments are welcomed.

References ->

1) https://github.com/scoverage/sbt-scoverage

2) http://docs.scala-lang.org/overviews/core/string-interpolation.html

3) http://www.scala-lang.org/api/current/index.html#scala.StringContext

happy coding…


KNOLDUS-advt-sticker

This entry was posted in Scala and tagged . Bookmark the permalink.

3 Responses to Customizing String Interpolation – An Example

  1. Pingback: An Invitation From Scala String Interpolation | Knoldus

  2. Hello Sahil,
    You can ignore the implicit declaration of class below, if they are useless,

    object StringInterpolation {

    implicit class WriteScore(val sc: StringContext) extends AnyVal {

    def write(args: Any*): String = {

    checkLengths(sc.parts.length, args.length)
    val strings = sc.parts.iterator

    And you need not to assign the value to variable message in last statement as below,

    val buf = new StringBuffer(treatEscapes(strings.next))
    while (strings.hasNext) {
    buf.append(expressions.next)
    buf.append(treatEscapes(strings.next))
    }
    val message: String = writeToFile(buf.toString)
    message
    }

    Because in scala result of last statement is automatically returned.

    • answer to first question ->

      Using an implicit class provides a more convenient syntax for defining extension methods since we need not explicitly declare the WriteScore class objects (mentioned in blog). The call to :

      write”$name is $age years old\nand earns ₹ $salary”

      is transformed into(at compile time)

      new StringContext(“”, ” is “,” years old\nand earns ₹ “,””).write(name, age,salary)

      The implicit class is then used to rewrite it to the following:

      new WriteScore(new StringContext(“”, ” is “,” years old\nand earns ₹ “,””).write(name, age,salary))

      Now since the class is implicit the compiler implicitly create an instance of ‘WriteScore’ class at the compile time. (Generally a combo of implicit and any value class is used when we want to append functionalities provided by an already existing class)

      You may refer from the Scala documentation on String Interpolation for more information (my explanation is picked from the doc)

      answer to second question ->

      You are absolutely correct the value yielded by an expression in the last statement of a method is the return value of the method, well i just used the variable ‘message’ to show that the call to ‘writeToFile’ method returns a message denoting if write to the file was successfull or not. Though a comment would have been more efficient way to put the thing. :p

      Thank you very much for the Question, hope your issues are resolved 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s