[Scala] Scala 2.10 新功能--字串取代

String Interpolator

話說寫 Ruby 這類程式語言的時候,我們很常直接把變數塞進字串裡,而 Ruby 會自動幫我們把這個變數放進這個字串中,舉例來說當我們要把 name 這個變數塞到歡迎字串中的時候,會這樣寫:

irb(main):001:0> name = "Brian"
=> "Brian"
irb(main):002:0> puts "Hello, #{name}"
Hello, Brian
=> nil
irb(main):003:0>

但在 Java / Scala 的世界裡,由於沒有內建這類字串取代的功能,所以我們會用字串連接的運算子 + 或者是 "Hello, %s".format(....) 的方式來達成:

scala> val name = "Brian"
name: String = Brian

scala> println ("Hello, " + name)
Hello, Brian

scala> println ("Hello, %s".format(name))
Hello, Brian

可以看得出來,這樣的寫法比 Ruby 的寫法來的囉唆了些。

不過還好,Scala 2.10.0 終於把 String Interpolator 加進來囉,而且由於他是透過 Marco 的方式實作,所以是 Type-safe 的!

簡單的 s 字串取代

Scala 2.10.0 新加入了字串取代的功能,他的用法很簡單,在字串前面加個 s,並且在字串中把你需要的變數名稱加上 $ 字號就可以了:

scala> val name = "Brian"
name: String = Brian

scala> println(s"Hello, $name")
Hello, Brian

但倘若你還要針對變數做操作,那麼可以使用 ${...} 的形式,而 {} 裡面可以是任何合法的 Scala 運算式,他會把運算式的結果塞入字串中。

scala> val name = "Brian"
name: String = Brian

scala> println(s"Hello ${name.toUpperCase}")
Hello BRIAN

scala> println(s"Hello ${1 + 0.5}")
Hello 1.5

格式化的 f 字串取代

當然,有的時候我們會希望我們的字串是格式化過的,例如在整數前補零啦,向左或向右對齊之類的。現在 Scala 也提供了 f 字串取代來做到。

使用的方式和 s 字串取代相同,但在要取代的標地後面加上 Formatter 的格式。

scala> val id = 123
id: Int = 123

scala> println (f"Your serial number is $id%05d")
Your serial number is 00123

scala> println (f"Your serial number is ${id + 1234}%05d")
Your serial number is 01357

raw 的字串取代

Scala 裡提供了一個 """內容""" 的字串定義方式,這樣定義出的字串,不會去處理 escape sequence 的東西,例如 \n 不會被代換成換行符號。

scala> """Hello World\nHello World"""
res14: String = Hello World\nHello World

相同的,Scala 2.10.0 中提供了 raw 這個字串取代方式,他的做用和 """ 是一樣的--不去處理 escape sequence。

scala> val name = "Brian"
name: String = Brian

scala> println(raw"Hello,\n $name")
Hello,\n Brian

scala> println(raw"Hello,\n ${name.toUpperCase}")
Hello,\n BRIAN

一切都是 Type Safe 的

Scala 2.10.0 中的 String Interpolation 特別的地方,在於他是 Type-safe 的!

換句話說他的檢查是在 compile time 的時候發生,如果你指定的變數不存在,或是 {} 裡的運算式不符合 Scala 的型別檢查,他連 compile 都不會讓你過。XDDD

另一方面,在 Ruby 中,這種情況下他發生的是 Run-time Error,你會發現在發生 Error 的那行程式碼之前的程式都還是正常執行的。

# test.rb

name = "Brian"
puts "This is first Hello, #{name}"
puts "This is second Hello, #{nam}"
brianhsu@USBGentoo ~ $ ruby test.rb
This is first Hello, Brian
test.rb:5: undefined local variable or method `nam' for main:Object (NameError)
brianhsu@USBGentoo ~ $

但是在 Scala 2.10.0 裡面,String Interpolation 的檢查是發生在 compile-time 的:

val name = "Brian"

println (s"This is first Hello, $name")
println (s"This is second Hello, $nam")
brianhsu@USBGentoo ~ $ scala test.scala
/home/brianhsu/test.scala:4: error: not found: value nam
println (s"This is second Hello, $nam")
                                  ^
one error found

你可以看到,這個程式在 compile 的階段就被擋下來了,因為 Scala compiler 發現他根本找不到 nam 這個變數。

同樣的,f 字串取代裡的格式化也是 Type-safe 的,這比之前我們常用的 format 來得嚴格,因為 format 是 run-time 才做檢查,當形態有問題的時候是在執行時期吐出 Exception:

val name = "Brian"

println ("This is first Hello, %s" format(name))

// name 是字串,不是整數,但 compile 會過關,執行到這行程式死掉
println ("This is second Hello %10d" format(name))
brianhsu@USBGentoo ~ $ scala test.scala
This is first Hello, Brian
java.util.IllegalFormatConversionException: d != java.lang.String
        at java.util.Formatter$FormatSpecifier.failConversion(Formatter.java:4045)
        at java.util.Formatter$FormatSpecifier.printInteger(Formatter.java:2748)
        ....

但相對的,如果使用的是 f 字串取代,整個程式會在 compile time 的時候就被擋下來,因為他會發現你給的格式是整數用的 %10d,但是你塞給他的變數卻是字串,兩個的型別不同。

val name = "Brian"

// This is OK.
println (f"This is first Hello, $name%s")

// name 是字串,不是整數,所以 compiler 直接擋下來不給過
println (f"This is second Hello $name%10d")

// method name 打錯,同樣會被擋下來
println (f"This is third Hello ${name.toUppperCase}")
brianhsu@USBGentoo ~ $ scala test.scala
/home/brianhsu/test.scala:7: error: type mismatch;
 found   : String
 required: Int
println (f"This is second Hello $name%10d")
                                 ^
/home/brianhsu/test.scala:10: error: value toUppperCase is not a member of String
println (f"This is third Hello ${name.toUppperCase}")
                                      ^
two errors found

就這樣,透過 Scala 2.10.0 新增的 String interpolation,我們可以更加簡潔並安全地操作字串取代的功能,並且讓 compiler 幫我們檢查我們所使用的格式化方式與變數型態是不是相符。

回響