Scala 2.10中的字符串插值 - 如何插入一个字符串变量?

50

字符串插值是从Scala 2.10开始在Scala中提供的。

这是一个基本示例:

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

我想知道是否有一种动态插值的方法,例如以下内容(不能编译,仅供说明):

 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. 有没有一种“动态”评估这样的字符串的方法?(或者说这本质上是错误的/不可能的)

  2. 那么,s到底是什么?它不是一个方法定义显然它是StringContext上的一个方法),也不是一个对象(如果是对象,它会抛出不同于“未找到”的编译错误)

4个回答

39

s 实际上是 StringContext 上的一个方法(或者是可以从 StringContext 隐式转换得到的其他对象)。当你编写以下代码时:

whatever"Here is text $identifier and more text"

编译器对此进行解糖处理。
StringContext("Here is text ", " and more text").whatever(identifier)

默认情况下,StringContext 提供了 s, f, 和 raw* 方法。

正如你所看到的,编译器本身会挑选名称并将其传递给方法。由于这是在编译时发生的,因此您无法在运行时动态地进行它 - 编译器没有关于变量名称的信息。

然而,您可以使用 vars,以便可以交换您想要的值。而默认的 s 方法只调用 toString(正如您所期望的那样),因此您可以进行一些操作,例如

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已经被REPL调用了)。

这大概是你能做的最好的了。

* raw现在存在缺陷,预计要到2.10.1版本才会修复;只有变量之前的文本实际上是原始的(没有转义处理)。所以在2.10.1发布之前最好不要使用该功能,或者查看源代码并定义自己的解决方案。默认情况下,没有转义处理,因此定义自己的方案非常容易。


2
一个小补充。不仅仅是标识符可以使用。任何有效的 Scala 表达式都可以放在 ${} 中间。 - pedrofurla

14

这是在原问题的背景下,基于Rex的优秀回答,对#1提供的一个可能的解决方案。

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

4
使用 Scala 2.11.5 版本时,第二行代码会报错 "missing parameter type name"。我认为应该这样写:val template = (name:Any)=>s"Hello $name" 或者 def template(name:Any) = s"Hello $name" - Suma
你之前加了 val name = "World" 吗?我认为这有助于类型推断,如果没有它,你确实需要包含 name 的类型...换句话说,我不知道 $name 是否遮蔽了参数名... - Eran Medan
我将这三行代码粘贴到了Scala源文件中。如果我不给名称参数添加类型注释,就无法编译它。 - Suma
嗯...我想这可能是2.10版本中的一个错误...很好地捕捉到了! - Eran Medan

7
  1. 字符串插值发生在编译时,因此编译器通常没有足够的信息来插值 s(str)。根据 SIP,它需要一个字符串字面量。
  2. 在您提供的文档中的高级用法下,解释了形式为id“Hello $name .”的表达式在编译时被转换为new StringContext(“Hello”,“。”)。 id(name)

请注意,id可以是通过隐式类引入的用户定义的插值器。文档提供了一个json插值器的示例。

implicit class JsonHelper(val sc: StringContext) extends AnyVal {
  def json(args: Any*): JSONObject = {
    ...
  }
}

1
在当前的实现中,这是本质上不可能的:局部变量名在执行时不可用——可能会作为调试符号保留,但也可能已经被剥离。(成员变量名是可用的,但这不是你在这里描述的情况)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接