Scala - String Interpolation (s, f, raw)
The `s` interpolator is the most commonly used string interpolator in Scala. It allows you to embed variables and expressions directly into strings using the `$` prefix.
Key Insights
- Scala provides three built-in string interpolators:
sfor simple variable substitution,ffor formatted output with type safety, andrawfor literal strings without escape sequence processing - The
sinterpolator supports arbitrary expressions in${}blocks and evaluates them at runtime, whilefperforms compile-time type checking against format specifiers - Custom interpolators can be implemented by defining implicit classes with methods matching the interpolator prefix, enabling domain-specific string processing
The s Interpolator: Expression Evaluation
The s interpolator is the most commonly used string interpolator in Scala. It allows you to embed variables and expressions directly into strings using the $ prefix.
val name = "Alice"
val age = 30
val greeting = s"Hello, $name. You are $age years old."
// Result: "Hello, Alice. You are 30 years old."
For complex expressions, wrap them in curly braces:
val price = 29.99
val quantity = 3
val total = s"Total cost: ${price * quantity}"
// Result: "Total cost: 89.97"
case class User(firstName: String, lastName: String)
val user = User("John", "Doe")
val message = s"Welcome, ${user.firstName} ${user.lastName}!"
// Result: "Welcome, John Doe!"
The s interpolator evaluates expressions at runtime and calls toString on the results. This makes it flexible but means you lose compile-time formatting guarantees:
val numbers = List(1, 2, 3)
val output = s"Numbers: ${numbers.mkString(", ")}"
// Result: "Numbers: 1, 2, 3"
val condition = true
val status = s"Status: ${if (condition) "active" else "inactive"}"
// Result: "Status: active"
The f Interpolator: Type-Safe Formatting
The f interpolator provides printf-style formatting with compile-time type checking. It ensures that format specifiers match the types of the interpolated values.
val pi = 3.14159
val formatted = f"Pi to 2 decimal places: $pi%.2f"
// Result: "Pi to 2 decimal places: 3.14"
val count = 42
val hex = f"Decimal: $count%d, Hex: $count%x"
// Result: "Decimal: 42, Hex: 2a"
The format specifier follows the variable name after a % symbol. Common specifiers include:
val num = 123.456
val examples = List(
f"$num%f", // 123.456000 (default float)
f"$num%.2f", // 123.46 (2 decimal places)
f"$num%10.2f", // " 123.46" (width 10, right-aligned)
f"$num%-10.2f", // "123.46 " (width 10, left-aligned)
f"$num%e" // 1.234560e+02 (scientific notation)
)
val integer = 255
val intFormats = List(
f"$integer%d", // 255 (decimal)
f"$integer%x", // ff (hexadecimal)
f"$integer%o", // 377 (octal)
f"$integer%05d" // 00255 (zero-padded to width 5)
)
Type safety is enforced at compile time:
val text = "hello"
// f"$text%d" // Compile error: type mismatch
val number = 42
val safe = f"$number%d" // Compiles successfully
For complex formatting scenarios:
case class Product(name: String, price: Double, quantity: Int)
val product = Product("Laptop", 999.99, 5)
val invoice = f"""
|Product: ${product.name}
|Price: $$${product.price}%.2f
|Quantity: ${product.quantity}%d
|Total: $$${product.price * product.quantity}%.2f
""".stripMargin
Note the escaped $$ to produce a literal dollar sign in the output.
The raw Interpolator: Preserving Escape Sequences
The raw interpolator works like s but does not process escape sequences in the string literal itself. This is useful when you need to preserve backslashes and other special characters.
val path = raw"C:\Users\Documents\file.txt"
// Result: "C:\Users\Documents\file.txt"
// Compare with s interpolator:
val pathS = s"C:\Users\Documents\file.txt"
// Result: "C:UsersDocumentsfile.txt" (escape sequences processed)
The distinction is important for regular expressions and file paths:
val regex = raw"\d{3}-\d{2}-\d{4}"
// Result: "\d{3}-\d{2}-\d{4}" (literal backslashes preserved)
val regexS = s"\d{3}-\d{2}-\d{4}"
// Result: "d{3}-d{2}-d{4}" (backslashes interpreted as escapes)
However, raw still evaluates interpolated expressions:
val username = "admin"
val windowsPath = raw"C:\Users\$username\Desktop"
// Result: "C:\Users\admin\Desktop"
val newline = "\n"
val mixed = raw"Line1${newline}Line2"
// Result: "Line1
// Line2"
// The interpolated variable still contains a processed newline
This behavior makes raw suitable for scenarios where the string literal contains backslashes but you still need variable substitution:
val tableName = "users"
val sqlQuery = raw"SELECT * FROM $tableName WHERE name LIKE '%\_%'"
// Result: "SELECT * FROM users WHERE name LIKE '%\_%'"
// The \_ is preserved for SQL LIKE pattern matching
Multiline Strings with Interpolation
All three interpolators work with triple-quoted strings for multiline content:
val name = "Bob"
val age = 25
val multiline = s"""
|Name: $name
|Age: $age
|Status: ${if (age >= 18) "Adult" else "Minor"}
""".stripMargin
val table = f"""
|${"Name"}%-10s | ${"Age"}%3s | ${"Score"}%5s
|${"-" * 10}%-10s | ${"-" * 3}%3s | ${"-" * 5}%5s
|${"Alice"}%-10s | ${30}%3d | ${95.5}%5.1f
|${"Bob"}%-10s | ${25}%3d | ${87.3}%5.1f
""".stripMargin
Custom Interpolators
You can create custom interpolators by defining an implicit class with a method matching the interpolator name:
implicit class JsonInterpolator(val sc: StringContext) extends AnyVal {
def json(args: Any*): String = {
val strings = sc.parts.iterator
val expressions = args.iterator
val buf = new StringBuilder(strings.next())
while (strings.hasNext) {
val arg = expressions.next()
val jsonValue = arg match {
case s: String => s""""$s""""
case n: Int => n.toString
case n: Double => n.toString
case b: Boolean => b.toString
case null => "null"
case _ => s""""${arg.toString}""""
}
buf.append(jsonValue)
buf.append(strings.next())
}
buf.toString
}
}
val name = "Alice"
val age = 30
val active = true
val result = json"""{"name": $name, "age": $age, "active": $active}"""
// Result: """{"name": "Alice", "age": 30, "active": true}"""
Custom interpolators enable domain-specific languages and specialized formatting:
implicit class SqlInterpolator(val sc: StringContext) extends AnyVal {
def sql(args: Any*): String = {
// Implement SQL escaping and parameterization
sc.parts.zip(args).map { case (part, arg) =>
part + escapeSQL(arg)
}.mkString + sc.parts.last
}
private def escapeSQL(arg: Any): String = arg match {
case s: String => s"'${s.replace("'", "''")}'"
case other => other.toString
}
}
String interpolation in Scala provides a powerful, type-safe mechanism for constructing strings. Choose s for general-purpose interpolation, f when you need formatting control with type safety, and raw when working with strings containing literal backslashes. For specialized use cases, custom interpolators extend this functionality to match your domain requirements.