Scala:match表達式、break和continue
match表達式
Scala的匹配表達式允許你在許多可選項:alternative中做選擇,就好象其它語言中的switch語句。通常說來match表達式可以讓你使用任意的模式:pattern做選擇。通用的模式可以稍等再說。目前,只要考慮使用match在若干可選項中做選擇。
51CTO編輯推薦:Scala編程語言專題
作為例子,代碼7.14里的腳本從參數列表讀入食物名然后打印食物配料。match表達式檢查參數列表的第一個參數firstArg。如果是字串"salt",就打印"pepper",如果是"chips",就打印"salsa",如此遞推。缺省情況用下劃線(_)說明,這是常用在Scala里作為占位符表示完全不清楚的值的通配符。
代碼 7.14 有副作用的match表達式
- val firstArg = if (args.length > 0) args(0) else ""
- firstArg match {
- case "salt" => println("pepper")
- case "chips" => println("salsa")
- case "eggs" => println("bacon")
- case _ => println("huh?")
- }
與Java的switch語句比,匹配表達式還有一些重要的差別。其中之一是任何種類的常量,或其他什么東西,都能用作Scala里的case,而不只是Java的case語句里面的整數類型和枚舉常量。在這個例子里,可選項是字串。另一個區別是在每個可選項的最后并沒有break。取而代之,break是隱含的,不會有從一個可選項轉到另一個里面去的情況。這通常把代碼變短了,并且避免了一些錯誤的根源,因為程序員不再因為疏忽在選項里轉來轉去。
然而,與Java的switch相比最顯著的差別,或許是match表達式也能產生值。在前一個例子里,match表達式的每個可選項打印輸出一個值。只生成值而不是打印也可以一樣做到,展示在代碼7.15中。match表達式產生的值儲存在friend變量里。這除了能讓代碼變得更短之外(至少減少了幾個指令),還解開了兩個不相干的關注點:首先選擇食物名,其次打印它。
- 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)
代碼 7.15 生成值的match表達式
離開break和continue
你可能注意到了這里沒有提到過break和continue。Scala去掉了這些命令因為他們與函數式文本,下一章會談到這個特征,嚙合得不好。continue在while循環中的意思很清楚,但是在函數式文本中表示什么呢?雖然Scala既支持指令式風格也支持函數式風格,但在這點上它略微傾向于函數式編程從而換得在語言上的簡潔性。盡管如此,請不要著急。有許多不用break和continue的編程方式,如果你能有效利用函數式文本,就能比原來的代碼寫得更短。
最簡單的方式是用if替換每個every和用布爾變量替換每個break。布爾變量指代是否包含它的while循環應該繼續。比如說,假設你正搜索一個參數列表去查找以“.scala”結尾但不以連號開頭的字串。Java里你可以——如果你很喜歡while循環,break和continue——如此寫:
- int i = 0; // 在Java中……
- boolean foundIt = false;
- while (i < args.length) {
- if (args[i].startsWith("-"))
- {
- i = i + 1;
- continue;
- }
- if (args[i].endsWith(".scala")) {
- foundIt = true;
- break;
- }
- i = i + 1;
- }
如果要字面直譯成Scala的代碼,代之以執行一個if然后continue,你可以寫一個if環繞while余下的全部內容。要去掉break,你可以增加一個布爾變量提示是否繼續做下去,不過在這里你可以復用foundIt。使用這兩個技巧,代碼就可以像代碼7.16這樣完成了:
- 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
- }
代碼 7.16 不帶break或continue的循環
這個版本與原來的Java代碼非常像。所有的主要段落仍然存在并保持原順序。有兩個可重新賦值的變量及一個while循環。循環內有個i是否小于args.length的測試,然后檢查"-",然后檢查".scala"。
如果要去掉代碼7.16里面的var,你可以嘗試的一種方式是用遞歸函數重寫循環。比方說,你可以定義帶一個整數值做輸入的searchFrom函數,向前搜索,并返回想要的參數的索引。采用這種技巧的代碼看上去會像展示在代碼7.17中這樣的:
- 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)
代碼 7.17 不用var做循環的遞歸替代方法
代碼7.17的版本提供了一個能夠看得懂的名字說明這個函數在做什么,它用遞歸替代了循環。每個continue都被帶有i + 1做參數的遞歸調用替換掉,有效地跳轉到下一個整數。許多人都發現當他們開始使用遞歸后,這種編程風格更易于理解。
注意
Scala編譯器不會實際對代碼7.17展示的代碼生成遞歸函數。因為所有的遞歸調用都在尾調用:tail-call位置,編譯器會產生出與while循環類似的代碼。每個遞歸調用將被實現為回到函數開始位置的跳轉。
【相關閱讀】