通常在撰寫迴圈時,最直覺的寫法,就是帶入變數,隨時在迴圈中檢查變數的值,也可以利用變數控制,是否要跳出迴圈,但 functional style programming 的重點,除了希望 programmer 能用更易讀的方式撰寫程式,同時希望程式的效能,可以得到顯著的提昇,因此消滅不必要的迴圈與變數,成了 functional programming 的另一項重要任務,而 java programmer 要學習的,是忘記那些 OO Design Patterns,讓程式碼更精簡。
Java 的定位是商用語言,以往的歷史也證實,在 Server Side 的運算環境中,採用 Java J2EE solution 是個很好的選擇,但也因為是 Server Side 的語言,java programmer 比較不在意使用了多少變數,消耗了多少記憶體,著眼點通常放在要把功能做好,模組切割要合理,系統要穩定,只要選個好一些 Server 多一些的記體體就可以運作了。
Function Programming 讓我重新回到撰寫 C 語言程式碼的心情,C 語言追求卓越的速度,在意自己使用了多少記憶體,有時候甚至還要動用 assembly,相容於 Java 的 Scala 是個雙面人,我們該沿用 Java 帶來的物件導向分析習慣,將系統模組化,接下來在實作時,思考如何撰寫高效率的 FP 程式。
if expression
var filename = "default.txt"
if (!args.isEmpty)
filename = args(0)
如果換個寫法,就可以不需要使用 var,程式也比較短,使用 val 也比較接近 functional style,要使用 variable 之前,要先想一下是不是可以 改寫成 expression,另外要盡可能使用 val,可讓程式更容易 refactor。
val filename =
if (!args.isEmpty) args(0)
else "default.txt"
while loops
unit value 寫成 (),() 的存在,就是跟 java void 不同的地方,因為 greet() 會回傳 Unit, 所以 greet() 就會等於 ()。
scala> def greet() { println("hi") }
greet: ()Unit
scala> greet() == ()
hi
res0: Boolean = true
以 java 的習慣,可能會這樣寫
var line = ""
while ((line = readLine()) != "")
println("Read: "+ line)
但因為 line = readLine() 的結果是 () Unit,而 Unit 一定不會等於 "",所以就會永遠是 true
var line = ""
do {
line = readLine()
println("Read: "+ line)
} while (line != "")
使用 recursion 取代 while
如果要計算 g.c.d 可以這樣寫
def gcdLoop(x: Long, y: Long): Long = {
var a = x
var b = y
while (a != 0) {
val temp = a
a = b % a
b = temp
}
b
}
改用 functional style: recursion 撰寫 gcd, 同時可以省去很多不需要的vars。
def gcd(x: Long, y: Long): Long =
if (y == 0) x else gcd(y, x % y)
generator
val filesHere = (new java.io.File(".")).listFiles
for (file <- filesHere)
println(file)
file <- filesHere 的語法稱為 generator
如果要對一堆檔案重複進行某個運算,也許我們會很直覺地這樣寫
for (i <- 0 to filesHere.length - 1)
println(filesHere(i))
這種寫法的缺點是需要產生一個變數 i,因此這種寫法,在 scala 是不常見的,我們必須修改成,直接對 file collection 做 iteration,而 iteration 可能有下面四種問題:filtering、nested iteration、mid-stream variable binding、producing a new collection。
filtering
取得檔名是 .scala 結尾的 檔案
val filesHere = (new java.io.File(".")).listFiles
for (file <- filesHere if file.getName.endsWith(".scala"))
println(file)
也可以使用兩個以上的 filter
for (
file <- filesHere
if file.isFile
if file.getName.endsWith(".scala")
) println(file)
nested iteration
在 for 裡面,也可以同時使用多個 generator
def fileLines(file: java.io.File) =
scala.io.Source.fromFile(file).getLines().toList
def grep(pattern: String) =
for (
file <- filesHere
if file.getName.endsWith(".scala");
line <- fileLines(file)
if line.trim.matches(pattern)
) println(file +": "+ line.trim)
grep(".*gcd.*")
mid-stream variable binding
上面的程式碼,重複執行了 line.trim,如果想要只運算一次,就要產生一個變數 trimmed = line.trim,trimmed 用了兩次, 一次在 if, 一次在 println。
def grep(pattern: String) =
for {
file <- filesHere
if file.getName.endsWith(".scala")
line <- fileLines(file)
trimmed = line.trim
if trimmed.matches(pattern)
} println(file +": "+ trimmed)
grep(".*gcd.*")
producing a new collection
用 yield, 可以把過濾後的結果 array 紀錄起來,結果會得到 Array[File], 因為 filesHere 是 array, file 是 File。yield 必須放在 for 的外面,語法為 for clauses yield body。
def scalaFiles =
for {
file <- filesHere
if file.getName.endsWith(".scala")
} yield file
try - catch - finally
在 finally 中關閉 file, 確保檔案一定有被關閉,scala 可以在 finally 裡面 return value, 但會把結果覆蓋掉。
val file = new FileReader("input.txt")
try {
// Use the file
} finally {
file.close()
}
match expression
類似 java 的 switch,預設是 _ ,每個 case 裡面不需要寫 break,不只可以用 int, enum, 也可以用 string,可直接將 match 結果,儲存到變數中。
val firstArg = if (!args.isEmpty) args(0) else ""
val friend =
firstArg match {
case "salt" => "pepper"
case "chips" => "salsa"
case "eggs" => "bacon"
case _ => "huh?"
}
println(friend)
no break and continue
因為 scala 沒有 break 跟 continue,最簡單的方式,就是把 continue 換成 if,把 break 換成 boolean 變數。
var i = 0
var foundIt = false
while (i < args.length && !foundIt) {
if (!args(i).startsWith("-")) {
if (args(i).endsWith(".scala"))
foundIt = true
}
i = i + 1
}
更好的寫法是 recursive 呼叫 seachFrom
def searchFrom(i: Int): Int =
if (i >= args.length) -1
else if (args(i).startsWith("-")) searchFrom(i + 1)
else if (args(i).endsWith(".scala")) i
else searchFrom(i + 1)
val i = searchFrom(0)
Variable scope
scala 的 scoping rule 跟 java 大部分都一樣
只有一點不同,scala 可在 nested scopes 中定義同樣的 variable name。
val a = 1;
{
val a = 2 // Compiles just fine
println(a)
}
println(a)
但是在 scala interpreter 中,看起來雖然像是同一個 scope,但實際上是不同的,因此在 interpreter 中,變數可重複定義。
scala> val a = 1
a: Int = 1
scala> val a = 2
a: Int = 2
scala> println(a)
2
Reference
Programming In Scala by Martin Odersky, Lex Spoon, and Bill Venners
沒有留言:
張貼留言