Scala:如何編寫新的控制結構
擁有第一類函數的語言中,即使語言的語法是固定的,你也可以有效地制作新的控制結構。所有你需要做的就是創建帶函數做參數的方法。例如,下面是“雙倍”控制結構,能夠重復一個操作兩次并返回結果:
- scala> def twice(op: Double => Double, x: Double) = op(op(x))
- twice: ((Double) => Double,Double)Double
- scala> twice(_ + 1, 5)
- res9: Double = 7.0
51CTO編輯推薦:Scala編程語言專題
這個例子里op的類型是Double => Double,就是說它是帶一個Double做參數并返回另一個Double的函數。
任何時候你發現你的代碼中多個地方有重復的控制模式,你就應該考慮把它實現為一個新的控制結構。本章早些時候你看到了filesMatching,一個極度特化了的控制模式?,F在考慮一個更寬泛使用的代碼模式:打開一個資源,對它進行操作,然后關閉資源。你可以使用如下的方法將其捕獲并放入控制抽象:
有了這個方法,你就可以這樣使用:
- def withPrintWriter(file: File, op: PrintWriter => Unit) {
- val writer = new PrintWriter(file)
- try {
- op(writer)
- } finally {
- writer.close()
- }
- }
使用這個方法的好處是,由withPrintWriter而不是用戶的代碼,確認文件在結尾被關閉。因此忘記關閉文件是不可能的。這個技巧被稱為貸出模式:loan pattern,因為控制抽象函數,如withPrintWriter,打開了資源并“貸出”給函數。例如,前面例子里的withPrintWriter把PrintWriter借給函數op。當函數完成的時候,它發出信號說明它不再需要“借”的資源。于是資源被關閉在finally塊中,以確信其確實被關閉,而忽略函數是正常結束返回還是拋出了異常。
- withPrintWriter(
- new File("date.txt"),
- writer => writer.println(new java.util.Date)
- )
讓客戶代碼看上去更像內建控制結構的一種方式是使用大括號代替小括號包圍參數列表。Scala的任何方法調用,如果你確實只傳入一個參數,就能可選地使用大括號替代小括號包圍參數。例如,代之以:
你可以寫成:
- scala> println("Hello, world!")
- Hello, world!
在第二個例子里,你使用了大括號替代小括號包圍println的參數。然而,這個大括號技巧僅在你傳入一個參數時有效。下面是破壞這個規則的嘗試:
- scala> println { "Hello, world!" }
- Hello, world!
因為你正打算把兩個參數傳入substring,當你嘗試用大括號保衛這些參數的時候產生了錯誤。為了糾正錯誤,你需要使用小括號:
- scala> val g = "Hello, world!"
- g: java.lang.String = Hello, world!
- scala> g.substring { 7, 9 }
- < console>:1: error: ';' expected but ',' found.
- g.substring { 7, 9 }
- ˆ
在傳入一個參數時可以用大括號替代小括號的機制的目的是讓客戶程序員能寫出包圍在大括號內的函數文本。這可以讓方法調用感覺更像控制抽象。以前面例子里定義的withPrintWriter方法舉例。在它最近的形式里,withPrintWriter帶了兩個參數,因此你不能使用大括號。雖然如此,因為傳遞給withPrintWriter的函數是列表的最后一個參數,你可以使用curry化把第一個參數,File拖入分離的參數列表。這將使函數僅剩下列表的第二個參數作為唯一的參數。代碼9.4展示了你要怎樣重新定義withPrintWriter。
- scala> g.substring(7, 9)
- res12: java.lang.String = wo
代碼 9.4 使用貸出模式寫文件
- def withPrintWriter(file: File)(op: PrintWriter => Unit) {
- val writer = new PrintWriter(file)
- try {
- op(writer)
- } finally {
- writer.close()
- }
- }
新的版本不同于舊版本的地方僅在于現在它有兩個參數列表每個里面有一個參數替代了原來的一個參數列表里面有兩個參數。僅比較這兩個參數的差異。展示在第130頁的withPrintWriter的前一個版本里,你看到了...File, op...。但在這個版本里,你看到了...File)(op...。有了上述的定義,你就可以用更賞心悅目的語法格式調用這個方法:
- val file = new File("date.txt")
- withPrintWriter(file) {
- writer => writer.println(new java.util.Date)
- }
這個例子里,第一個參數列表,包含了一個File參數,被寫成包圍在小括號中。第二個參數列表,包含了一個函數參數,被包圍在大括號中。
【相關閱讀】