2011年5月4日 星期三

4-5. 控制結構 --for expression

Scala 「for」 的使用方式與 Java 有相當大的不同。

Java 中的 for statement 的主要用法
1. loop:for (int i = 0; i < 100; i++) ...,就是這種用法
2. iteration:for (String s: strings) ...,就是這種用法

Scala 的 for expression 用在 collection 的 iteration,與 Java 的 iteration 用法比較像。但由於 Scala 支援函數式的用法,所以感覺起來,Scala 中 for expression,就比 Java 的 iteration 強上非常許多。

注意:Scala 並不支援 for (int i = 0; i < n; i++) 這種以 loop 為主要思考方式的  for statement。

for 在 Scala 中使用在 iteration,也就是將 collection 中的每個 element,一個個取出來,然後執行後面的程式碼。collection 是一個放置子資料的容器,容納許多 element,Scala 中,collection 都是一種 Sequence。Scala 有時稱 for expression 為 for comprehension,或是  sequence comprehension。

所謂 iteration 就是由 collection 中取出一個 element 後,執行一次後面的程式碼,這樣稱為一次 iteration。 for expression 可以運用在所有種類的 collection 中。

for 的功用非常強大,在 Scala 中受到相當的重視,需要仔細瞭解。我們將由簡單到複雜介紹。
for 的格式是「for (x <- list) code_block」。

一.對單一 collection 做 iteration
var lst = List(1, 2, 3) //List 我們尚未介紹,請先想像它與 Java 的 List 類似
for (n <- lst) {
  println(n)
}
n <- lst,表示將 lst 中的 element 一個個取出來,每次設定給 n,然後執行後面的程式碼。
n <- lst 這個東西稱為 generator,是 for 特有的用法。

對於 n,有幾點需要瞭解
1. n 是一個 val,所以不可重新 reassign,但 n 並不需宣告 val。若程式宣告 val n <- lst,會產生 deprecation warning,也就是不希望程式再宣告 val。
2. n 不需宣告 type,compiler 會使用 type inference。但你要宣告也是可以,只是多此一舉
上例改成 for (n: Int <- lst) {....} 是合法的。

二.對一段整數做 iteration
Java programmer 喜歡的 for (int i = 0; i < 100; i++)這種模式,可使用這種 collection 來取代
for (i <- 1 to 100) {
  println(i)
}
上例 1 to 100 會先執行,得到一個 Range 的 instance,這個 Range 的 instance,可以想像是一種 collection。「1 to 100」這一個 Range instance,可以想像為「1 到 100 的 collection」。for (i <- 1 to 100),就是「1 到 100」依序放入 i 中,然後執行。

至於, 1 to 100 為何會得到一個 Range 的 instance,我們會在未來提到,這裡先不提。

三.對 Array 裡面的 element 做 iteration
對 Array 裡面的 element 做 iteration,可以使用兩種方式,第一種是使用 index 來 iteration,第二種是直接對 element 來 iteration。

1. 對 index 做 iteration
val ary = Array(1, 2, 3) // Array 尚未介紹,其實這對應為 Java 的 int[]
for (i <- 0 until ary.length) println(ary[i])
使用 index 來對 Array 做 iteration,這種方式並不好,因為由 index ,再找到 element。Scala 的建議是,直接對 element 做 iteration。

2. 對 element 做 iteration
val ary = Array(1, 2, 3)
for (elm <- ary) println(elm)

四. 過濾(filter)
若我們不希望某些 element 進去 code_block,可以事先過濾。過濾的方式使用 if。 if 是 generator 的修飾,因此是 generator statement 的一部份,所以 if 之前不需有分號。
val ary = Array(1, 2, 3)
for (elm <- ary if elm < 3) // if 之前不需有小括號括起來
  println(elm)
有了 if statement 過濾,不符合的 element,不會被 generate 出來。

當然也可在 code_block 中過濾
val ary = Array(1, 2, 3)
for (elm <- ary) {
if (elm < 3) println(elm) //在 code_block 中過濾
}
在 code_block 過濾,進入code_block的次數會比上一例的多,而且比較不好思考。
函數式的作法,通常是過濾,然後做 iteration。Scala 建議 filter 在 generator 時進行。

五. 多重過濾
若有需要多重的條件,可以使用多個 if statement,來做多重的過濾。這時的過濾條件,會變成兩個條件都成立才會 generate,也就是兩個條件做「and」運算。
val ary = Array(1, 2, 3)
for (
  elm <- ary
  if elm < 3 //第一次過濾
  if elm % 2 == 0 //第二次過濾
) println(elm)

六. 在 for expression 中宣告常數
在 for expression 中,可以宣告常數,讓程式較容易撰寫。
但 for expression 中不能宣告變數。

for (i <- 1 to 10; //這個分號是必須的,因為常數宣告與 generator 是不同的 statement
     n = i / 2 // 宣告一個常數 n,前面也可加 val
     var n1 = i / 2 // 這個 statement 是錯誤的,不能使用 var
   )
   println(n)
七. 多個 collection 的 iteration for expression 可以包含多個 generator,所以可以對多個 collection 做 iteration。 generate 的次序是,外面 collection 產生一個後,會對裡面的 collection 重新全部 generate 一遍。generate 出來的總共 element 是兩個 collection 的乘積。 兩個 generator 中間需要使用分號隔開(generator 與 generator 是不同的 statement)。
上例我們看到 generate 的次序。
val values = Array(1, 2, 3)
val types = Array("A", "B")
for (
  t <- types; //這裡的分號不能省,原因是小括號的分行不會做「分號推論」
  elm <- ary
) println("type="+ t + "elm=" + elm)
使用 filter 以及 內層 generator 參考外層 generator 的情況
for (
  i <- 1 to 5
  if i % 2 == 0; //這個分號不可少,因為小括號內不會做「分號推論」
  j <- i to 10 // 裡面那層的 generator,使用外層 generator 的資料
  if j % 3 == 0
) println(i * j)
八. for 的 generator 子句,其小括號可以使用大括號代替
val values = Array(1, 2, 3)
val types = Array("A", "B")
for { //換成大括號
  t <- types //這裡的分號可以省,原因是大括號的分行會做「分號推論」,所以原先需要分號,現在可以省略
  elm <- ary
} println("type="+ t + "elm=" + elm)
九. for 的 result value for 的完整格式為 「for (generators) yield body」, 但一般我們可以省略 yield,如前面的各個例子,都是省略 yield。 省略 yield 表示整個 for expression 的 result value 為 unit value。 若是我們希望 for expression 可以返回一串的資料,需要加上 yield。yield 會將後面 body 的 result value,收集在一個 collection 中。 for expression 結束時,這個 collection 就是 for expression 的 result value。 省略 yield,for expression 的 result 為 unit value。
使用 yield,for expression 的 result 為一個 collection。
使用 yield 加上 code_block
上例因為 println(i) 的 result 為 unit,所以產生三個 unit 的 collection。 使用 yield 加上 完整的 code_block
請注意:yield 要寫在整個的 code_block 之前,不可寫入 code_block 裡面。 yield 寫到 code_block 裡面,會導致錯誤
for expression 好東西,也是一個重要的東西,把基礎觀念弄清楚很重要。在這裡我們尚未提到 for 如何轉換成函數來執行,以後再提。 for expression 也被用在 continuation 中,雖然使用 for expression 的語法,但有點怪異,有興趣的朋友可自行查看「Scala與新的語言功能 --Continuation」。

沒有留言:

張貼留言