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

相關文章
相關標籤/搜索