50

String interpolation is available in Scala starting Scala 2.10

This is the basic example

 val name = "World"            //> name  : String = World
 val message = s"Hello $name"  //> message  : String = Hello World

I was wondering if there is a way to do dynamic interpolation, e.g. the following (doesn't compile, just for illustration purposes)

 val name = "World"            //> name  : String = World
 val template = "Hello $name"  //> template  : String = Hello $name
 //just for illustration:
 val message = s(template)     //> doesn't compile (not found: value s)
  1. Is there a way to "dynamically" evaluate a String like that? (or is it inherently wrong / not possible)

  2. And what is s exactly? it's not a method def (apparently it is a method on StringContext), and not an object (if it was, it would have thrown a different compile error than not found I think)


Suma
  • 33,181
  • 16
  • 123
  • 191
Eran Medan
  • 44,555
  • 61
  • 184
  • 276

4 Answers4

39

s is actually a method on StringContext (or something which can be implicitly converted from StringContext). When you write

whatever"Here is text $identifier and more text"

the compiler desugars it into

StringContext("Here is text ", " and more text").whatever(identifier)

By default, StringContext gives you s, f, and raw* methods.

As you can see, the compiler itself picks out the name and gives it to the method. Since this happens at compile time, you can't sensibly do it dynamically--the compiler doesn't have information about variable names at runtime.

You can use vars, however, so you can swap in values that you want. And the default s method just calls toString (as you'd expect) so you can play games like

class PrintCounter {
  var i = 0
  override def toString = { val ans = i.toString; i += 1; ans }
}

val pc = new PrintCounter
def pr[A](a: A) { println(s"$pc: $a") }
scala> List("salmon","herring").foreach(pr)
1: salmon
2: herring

(0 was already called by the REPL in this example).

That's about the best you can do.

*raw is broken and isn't slated to be fixed until 2.10.1; only text before a variable is actually raw (no escape processing). So hold off on using that one until 2.10.1 is out, or look at the source code and define your own. By default, there is no escape processing, so defining your own is pretty easy.

Elnur Abdurrakhimov
  • 44,533
  • 10
  • 148
  • 133
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • 2
    One small addition. Not only identifier can be used. Any valid scala expression can be put in between ${}. – pedrofurla Jul 26 '13 at 08:31
14

Here is a possible solution to #1 in the context of the original question based on Rex's excellent answer

val name = "World"                  //> name: String = World
val template = name=>s"Hello $name" //> template: Seq[Any]=>String = <function1>
val message = template(name)        //> message: String = Hello World
Eran Medan
  • 44,555
  • 61
  • 184
  • 276
  • 4
    With Scala 2.11.5 the second line gives error "missing parameter type name". I think it needs to be written as `val template = (name:Any)=>s"Hello $name"` or `def template(name:Any) = s"Hello $name"` – Suma Mar 02 '15 at 08:49
  • did you add the `val name = "World"` before? I think it helps the type inference, and without it you'll indeed need to include the type of name... in other words, I don't know if $name is shadowing the parameter name or not... – Eran Medan Mar 02 '15 at 16:47
  • I pasted all three lines into a Scala source. I cannot compile it unless I add type annotation to the name parameter. – Suma Mar 02 '15 at 17:46
  • Hm... I guess it was a bug in 2.10 or something... good catch! – Eran Medan Mar 03 '15 at 03:27
7
  1. String interpolation happens at compile time, so the compiler does not generally have enough information to interpolate s(str). It expects a string literal, according to the SIP.
  2. Under Advanced Usage in the documentation you linked, it is explained that an expression of the form id"Hello $name ." is translated at compile time to new StringContext("Hello", "."). id(name).

Note that id can be a user-defined interpolator introduced through an implicit class. The documentation gives an example for a json interpolator,

implicit class JsonHelper(val sc: StringContext) extends AnyVal {
  def json(args: Any*): JSONObject = {
    ...
  }
}
Kipton Barros
  • 21,002
  • 4
  • 67
  • 80
1

This is inherently impossible in the current implementation: local variable names are not available at execution time -- may be kept around as debug symbols, but can also have been stripped. (Member variable names are, but that's not what you're describing here).

jsalvata
  • 2,155
  • 15
  • 32