Scala LanguageString Interpolation

Remarks

This feature exists in Scala 2.10.0 and above.

Hello String Interpolation

The s interpolator allows the usage of variables within a string.

val name = "Brian"
println(s"Hello $name")

prints "Hello Brian" to the console when ran.

Formatted String Interpolation Using the f Interpolator

val num = 42d

Print two decimal places for num using f

println(f"$num%2.2f")
42.00

Print num using scientific notation using e

println(f"$num%e")
4.200000e+01

Print num in hexadecimal with a

println(f"$num%a")
0x1.5p5

Other format strings can be found at https://docs.oracle.com/javase/6/docs/api/java/util/Formatter.html#detail

Using expression in string literals

You can use curly braces to interpolate expressions into string literals:

def f(x: String) = x + x
val a = "A"

s"${a}"    // "A"
s"${f(a)}" // "AA"

Without the braces, scala would only interpolate the identifier after the $ (in this case f). Since there is no implicit conversion from f to a String this is an exception in this example:

s"$f(a)"  // compile-time error (missing argument list for method f)

Custom string interpolators

It is possible to define custom string interpolators in addition to the built-in ones.

my"foo${bar}baz"

Is expanded by the compiler to:

new scala.StringContext("foo", "baz").my(bar)

scala.StringContext has no my method, therefore it can be provided by implicit conversion. A custom interpolator with the same behavior as the builtin s interpolator would then be implemented as follows:

implicit class MyInterpolator(sc: StringContext) {
  def my(subs: Any*): String = {
    val pit = sc.parts.iterator
    val sit = subs.iterator
    // Note parts.length == subs.length + 1
    val sb = new java.lang.StringBuilder(pit.next())
    while(sit.hasNext) {
      sb.append(sit.next().toString)
      sb.append(pit.next())          
    }
    sb.toString
  }
}

And the interpolation my"foo${bar}baz" would desugar to:

new MyInterpolation(new StringContext("foo", "baz")).my(bar)

Note that there is no restriction on the arguments or return type of the interpolation function. This leads us down a dark path where interpolation syntax can be used creatively to construct arbitrary objects, as illustrated in the following example:

case class Let(name: Char, value: Int)

implicit class LetInterpolator(sc: StringContext) {
  def let(value: Int): Let = Let(sc.parts(0).charAt(0), value)
}

let"a=${4}" // Let(a, 4)
let"b=${"foo"}" // error: type mismatch
let"c=" // error: not enough arguments for method let: (value: Int)Let

String interpolators as extractors

It is also possible to use Scala's string interpolation feature to create elaborate extractors (pattern matchers), as perhaps most famously employed in the quasiquotes API of Scala macros.

Given that n"p0${i0}p1" desugars to new StringContext("p0", "p1").n(i0), it is perhaps unsurprising that extractor functionality is enabled by providing an implicit conversion from StringContext to a class with property n of a type defining an unapply or unapplySeq method.

As an example, consider the following extractor which extracts path segments by constructing a regular expression from the StringContext parts. We can then delegate most of the heavy lifting to the unapplySeq method provided by the resulting scala.util.matching.Regex:

implicit class PathExtractor(sc: StringContext) {
  object path {
    def unapplySeq(str: String): Option[Seq[String]] =
      sc.parts.map(Regex.quote).mkString("^", "([^/]+)", "$").r.unapplySeq(str)      
  }
}

"/documentation/scala/1629/string-interpolation" match {
  case path"/documentation/${topic}/${id}/${_}" => println(s"$topic, $id")
  case _ => ???
}

Note that the path object could also define an apply method in order to behave as a regular interpolator as well.

Raw String Interpolation

You can use the raw interpolator if you want a String to be printed as is and without any escaping of literals.


println(raw"Hello World In English And French\nEnglish:\tHello World\nFrench:\t\tBonjour Le Monde")

With the use of the raw interpolator, you should see the following printed in the console:


Hello World In English And French\nEnglish:\tHello World\nFrench:\t\tBonjour Le Monde

Without the raw interpolator, \n and \t would have been escaped.


println("Hello World In English And French\nEnglish:\tHello World\nFrench:\t\tBonjour Le Monde")

Prints:


Hello World In English And French
English:       Hello World
French:        Bonjour Le Monde