simple and typesafe interpolated strings, checked at compile-time

Brian Clapper, @brianclapper

What is Contextual?

  • Allows you to define your own string interpolators
  • ... that are checked at compile time

What is a "string interpolator"?

You've seen them before. Some are built into Scala:

Simple string interp:

          val age = 10
          val name = "Jenna"
          println(s"My name is $name, and I am $age years old.")

printf-style interp (checked at compile time!):

        println(f"My name is $name%20s, and I am $age%2d years old.")

Other examples

  • Apache Spark's DataFrame API provides a "$" interpolator as a convenient column reference: $"name", $"salary"
  • The Slick API has a SQL interpolator: sql"SELECT * FROM ..."

You can write your own

As noted at , whenever the compiler sees


it looks for an id() method on StringContext. If such a method is found, the compiler parses the string into prefixes and arguments, instantiating a StringContext with the prefixes and passing the arguments into the id() method.

You can write your own

What the hell does that mean?

Suppose you code this:

      json"{ name: $name, id:$id }"

The compiler converts that to:

      new StringContext("{name: ", " id: ", "}").json(name, id)

Let's take a look at an example

Time for some code...

How do you get compile-time evaluation?

The previous example failed at runtime. How do you implement a string interpolator that fails at compile time, the way the "f" interpolator does?

Short answer: Compiler macros.

Macros can be quite complicated. They expose the compiler's AST, and the macro writer is often required to manipulate the AST and perform type-level programming.

There's an easier way.

It's called Contextual.

Contextual hides all the macro fiddling, providing a simpler (though not entirely simplistic) API that allows you to specify the compile-time and runtime behavior of your interpolator.

How does it work?

Basic approach:

  • Add the necessary imports.
  • Create an object that subclasses Interpolator.
  • Implement the contextualize() method to provide any compile-time checks you want.
  • Implement the evaluate() method to provide the runtime behavior.
  • Create an implicit class to add the appropriate interpolator method to the Scala StringContext class.

A Contextual "regex" interpolator

Let's reimplement our "regex" interpolator in Contextual. (Time for more code.)

What about substitution?

Let's look at an "sh" interpolator that:

  • allows embedded quotes
  • supports compile-time syntax checks
  • allows substitution (with some restrictions)

We'll also look at a class that removes that final restriction.

(Back to IntelliJ.)


This API isn't exactly simple. But if you want compile-time interpolator checks, it's far simpler than the alternative.

There's a Gitter channel devoted to Contextual, where you can ask questions.

Jon Pretty is a very nice fellow. Don't be afraid to ask questions. Also, don't be afraid to suggest updates.