Scala2.10新特性之 String Interpolation

String Interpolation

http://docs.scala-lang.org/overviews/core/string-interpolation.html html

2013-1-7
(英语四级未过,借助各类词典、翻译,历时两个晚上,终于翻译完了,若有翻译错误或用词不当,欢迎指正) java

介绍

从 Scala2.10.0 开始,Scala提供一个新的机制,经过你的数据建立字符串:字符串插值(String Interpolation)。它容许用户将变量的引用直接嵌入处处理字符串字面量(processed string literals)中。例如: express

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 ". 这句没太看懂) json

用法

Scala提供了三个开箱即用的字符串插入方法:s,f和raw 数组

插值器s

在任何字符串字面量前追加s,这个字符串就容许直接包含变量。前面已经看过示例。
在示例中,$name被嵌入到一个处理字符串中,插值器s知道变量在处理字符串中的位置,并将变量的值插入其中。
字符串插值器也能够包含任意表达式(经过${})。例如: 安全

println(s"1 + 1 = ${1 + 1}")

-------------------------
实际上,s是一个case class StringContext的成员方法。
看另外一个例子: app

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。来看一个例子: ide

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类似,不一样的是它不对字符串字面量执行转义。这有一个例子: ui

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版本。