String Interpolation
http://docs.scala-lang.org/overviews/core/string-interpolation.html
2013-1-7
(英语四级未过,借助各种词典、翻译,历时两个晚上,终于翻译完了,如有翻译错误或用词不当,欢迎指正)
介绍
从 Scala2.10.0 开始,Scala提供一个新的机制,通过你的数据创建字符串:字符串插值(String Interpolation)。它允许用户将变量的引用直接嵌入到处理字符串字面量(processed string literals)中。例如:
val name = "James"
println(s"Hello, $name") // Hello, James
上文中,s"Hello, $name"是一个处理字符串字面量,这意味着编译器做了一些额外的工作。处理字符串字面量被表示为一个在"(双引号)之前的字符集合(原文:A processed string literal is denoted by a set of characters precedding the ". 这句没太看懂)
用法
Scala提供了三个开箱即用的字符串插入方法:s,f和raw
插值器s
在任何字符串字面量前追加s,这个字符串就允许直接包含变量。前面已经看过示例。
在示例中,$name被嵌入到一个处理字符串中,插值器s知道变量在处理字符串中的位置,并将变量的值插入其中。
字符串插值器也可以包含任意表达式(通过${})。例如:
println(s"1 + 1 = ${1 + 1}")
-------------------------
实际上,s是一个case class StringContext的成员方法。
看另一个例子:
val name = "James"
val age = 22
println(s"$name is ${age + 2} years old.")
通过scalac -Xprint:cleanup输出可以得到:
new StringContext(
scala.this.Predef.wrapRefArray(
Array[String]{"", " is ", " years old."}.$asInstanceOf[Array[Object]]()
)
).s(
scala.this.Predef.genericWrapArray(
Array[Object]{name, scala.Int.box(age.+(2))}
)
)
从上面的抽象语法树中可以看出,编译器实际上将字符串从变量引入的地方截断,然后将字面量和变量引用分别放到两个数组中,然后重新组合(StringContext#standardInterpolator)
-------------------------
插值器f
在任何字符串字面量前追加f,就可以创造一个简单的格式化字符串,类似于其他语言中的printf(scala里不是也有吗?)。当使用插值器f时,所有变量的引用应该跟随printf风格的格式字符串,如同%d。来看一个例子:
val height = 1.9d
val name = "James"
println(f"$name%s is $height%2.2f meters tail") // James is 1.90 meters tall(好高啊)
插值器f是类型安全的。如果你试图传递一个只能工作于整数的格式化字符串,却又传了一个浮点数,编译器会发出一个错误。例如
val height: Double = 1.9d
scala> f"$height%4d"
<console>:9: error: type mismatch;
found : Double
required: Int
f"$height%4d"
插值器f利用Java的字符串格式工具(The f interpolator makes use of the string format utilities available from Java.)。字符%后允许的格式在Formatter javadoc中有概述。如果一个变量没有定义格式器,那么就假设它是%s(String)(即f"$name"等同于f"$name%s")。
插值器raw
插值器raw和插值器s相似,不同的是它不对字符串字面量执行转义。这有一个例子:
scala> s"a\nb"
res0: String =
a
b
插值器s将字符\n替换成了回车符。而插值器raw不会这么做。
scala> raw"a\nb"
res1: String = a\nb
当你想要避免有表达式(例如\n变成回车)时,插值器raw是很有用的。
---------------------------
在继续之前,我们再来比较一下插值器与字符串
val s1 = "a\nb"
val s2 = """a\nb"""
val s3 = s"a\nb"
val s4 = f"a\nb"
val s5 = raw"a\nb"
编译后可以得到:
val s1: String = "a\nb";
val s2: String = "a\\nb";
val s3: String = new StringContext(
scala.this.Predef.wrapRefArray(
Array[String]{"a\\nb"}.$asInstanceOf[Array[Object]]()
)
).s(immutable.this.Nil);
val s4: String = {
new collection.immutable.StringOps(
scala.this.Predef.augmentString("a\nb")
).format(immutable.this.Nil)
};
val s5: String = new StringContext(
scala.this.Predef.wrapRefArray(
Array[String]{"a\\nb"}.$asInstanceOf[Array[Object]]()
)
).raw(immutable.this.Nil);
从s4可以看出插值器f其实就是format方法,因此编译得到的字符串与普通字符串s1相同。
插值器raw(s5)相当于原始字符串,编译得到的也与原始字符串s2相同。
只是插值器s(s3)居然也编译成原始字符串(好吧,但愿不是我眼花),看来s方法还做了其他不可告人的事情。
---------------------------
除了三个默认的字符串插值器外,用户还可以自己来定义。
高级用法
在Scala中,所有处理字符串字面量都是简单的代码转换。每当编译器遇到如下形式的字符串字面量:
id"string content"
编译器把它转换成StringContext实例的一个方法调用(id)。这个方法也可以在隐式作用域内。要定义自己的字符串插值,我们需要简单地创建一个隐式类并且添加一个新方法到StringContext。这有一个例子:
// Note: We extends AnyVal to prevent runtime instantiation. See
// value class guide for more info.
implicit class JsonHelper(val sc: StringContext) extends AnyVal {
def json(args: Any*): JSONObject = sys.error("TODO - IMPLEMENT")
}
def giveMeSomeJson(x: JSONObject): Unit = ...
giveMeSomeJson(json"{ name: $name, id: $id }")
(呃...这里包含了隐式类和值类,都是Scala2.10的新特性。稍后可能会把这两个特性也翻译一下——如果看得懂的话。另外,implicit只能声明内部类,否则会报错,这点还不确认,等看完隐式类再说。)
在这个例子中,我们试图使用字符串插值创建一个JSON文字语法。隐式类JsonHelper必需在作用域内才可以使用这个语法,并且json方法需要完整地实现。无论如何,这个格式化字符串的结果将不是一个字符串,而是JSONObject。
当编译器遇到字符串json"{ name: $name, id: $id }",它将字符串重写为如下表达式:
new StringContext("{ name:", ",id: ", "}").json(name, id)
然后隐式类用于将它重写成如下形式:
new JsonHelper(new StringContext("{ name:", ",id: ", "}")).json(name, id)
这样,json方法可以访问原始块字符串,并将每一个表达式作为值。一个简单的方法实现可以是:
(the json method has access to the raw pieces of strings and each expression as a value. A simple (buggy) implementation of this method could be)
implicit class JsonHelper(val sc: StringContext) extends AnyVal {
def json(args: Any*): JSONObject = {
val strings = sc.parts.iterator
val expressions = args.iterator
var buf = new StringBuffer(strings.next)
while(strings.hasNext) {
buf append expressions.next
buf append strings.next
}
parseJson(buf)
}
}
(不过第5行还是用StringBuilder比较合适吧)
处理后的字符串中的每一部分都暴露在StringContext的parts成员中。每个表达式的值被传递给json方法的args参数。json方法得到它并产生一个很大的字符串,然后将其解析成JSON。一个更复杂的实现,可以避免生成这个字符串,并简单地从原始字符串和表达式的值直接构造JSON对象。
限制
字符串插值目前无法工作在模式匹配语句中。此功能是针对Scala的2.11版本。
来源:oschina
链接:https://my.oschina.net/u/580483/blog/100445