2014年2月17日 星期一

Scala basic4, For-comprehensions

前言
在Programming language裡面, 迴圈的設計能夠重複的執行某些工作, 藉此機器的運作才得以日復一日規律的執行, 如同世界運轉一般, 在Scala中迴圈 while 如JAVA一般沒甚麼不同, 可是For發展出更有彈性的應用, 尤其遇上了Immutable 變數的時候, 不能用過去 int i 當作迴圈次數索引, 需要有更方便的架構來幫助軟體運行

For-comprehensions

在Scala中 for 的定義也跟JAVA很類似, 主要由 for Keyword + 控制式 + Expression block組成

  • 控制式用來定義for 重複執行相關的條件, 比如重複次數, 重複終點等
  • Expression block就是for 要重複執行的任務本體

先把目光放在Scala中最常用到 for 的 pattern, Scala在資料結構上有很多種類的Collection可以善用, 比如上一章節的List, 透過 for loop 去循序存取整個Collection, 就得藉由Generator 的機制, 先看一個顯示以 .scala 為附檔名的檔案範例
val files = new java.io.File(".").listFiles
for(file <- files) { 
    val filename = file.getName
    if(fileName.endsWith(".scala")) println(file) 
}

Generator

比較會引人好奇的code 就是 file <- files, 在Scala中它就是Generator, 主要目的就是去循序存取整個Collection, 所以整個for loop會執行的次數就是 files這個Collection的長度, Generator的格式中,  <-的右邊是Collection 在範例中是 files, 在左邊的是會把在該次迴圈中從 files取出來的元素, 其實上面的code在JAVA中會如下
for(File file: files) {
      String filename = file.getName();
      if(filename.endsWith(".scala")) System.out.println(file);
} 
你會發現在Scala版本中的你不需要去指定從Collection中取出來的元素類型, 這也是受惠於Type inference的作用, Scala compiler會自動幫你辨別類型, 除了透過Generator之外, 你也可以這樣撰寫
for(
    file <- files; 
    fileName = file.getName; 
    if(fileName.endsWith(".scala"))
) println(file)
for-loop format
來看看上圖的 for-loop的format的定義, 在for 迴圈控制式裡面, 你可以放多個Enumerators, 個別Enumerator 在Scala官方中的定義是
 An enumerator is either a generator which introduces new variables, or it is a filter.
  • Generator就如同前面所述, 用來循序存取Collection的元素
  • Filter的作用就如同上面例子的if ( fileName.endsWith(".scala") ) 的功能, 用來過濾你要的 .scala 檔案(也是guards)
  • definitions就如同 fileName = file.getName; 你可以自己定義一些for-loop控制式所需的元件
  • 再簡化, 上例中 println(file) 還是 Expression block, 只是因為簡化成只有一行code, 大括號{ }都省略了
在Scala的for 控制式可以透過Generator, definition, filter的組合, 讓整個 for-loop 的運作上更有彈性, 不過要注意到, 在for expression中所創造的變數都是Immutable, 你不可任意更動.

巢狀迴圈的簡化

當你有個多維陣列, 你要一個個存取出陣列中所有的值, 最基本的做法就是巢狀迴圈, for-loop中再巢狀多個for-loop, 可是在Scala for-loop的format中, 你可放Generator來循序存取Collection中的值, 如果同時放多個Generator , 也是可行的, 並且有巢狀的效果, 看看以下例子
var a = Array(Array(1, 2, 3), Array(4, 5, 6), Array(7, 8, 9))
for( i<-a ; k <- i) print(k+" ")  //1 2 3 4 5 6 7 8 9
第一個Generator i <- a 把Array第一維度的元素Array(1 ,2, 3) , Array(4 ,5, 6) , Array(7 ,8, 9) 存取出來放到 i , 然後第二個Generator k <- i 把 第二維度的Array中的元素存取循序出來, 多個Generator同時放在同一個for 控制式裡面, 就會有巢狀迴圈效果

for-comprehensions of functional  form  

事實上, 在Scala中for-loop是有兩種類型

  • Imperative form, 就是會幫你重複執行你所指定的任務, 但不回傳任何結果
  • Function form, 在Function form中比較趨向於做數值運算相關的任務, 並最後回傳結果

比如去計算一個九九乘法所有可能結果,並且用字串存成一個九九乘法表
val a = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
val b = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
val c = for( f <- a, s <- b) yield f + " * " + s + " = " + f*s
//c: List[String] = List(1 * 1 = 1, 1 * 2 = 2...............)
c.foreach(println(_))
在這邊你會發現for的 expression block會變成由一個 yield keyword開頭,  yield keyword 會把 expression block的每一圈結果集合成一個List, 並回傳出來, 這樣的把結果存起來, 使其更有重複使用性與相容性,  這樣的點子也是來自於Functional Programming的概念, 再一個例子, 把function form的結果作更多應用
val xmlNode = {c.mkString(",")}
xmlNode: scala.xml.Elem = 1 * 1 = 1, 1 * 2 = 2...............
mkString是List的一個method, 它會把List中每一個元素都串接在一起成一個字串, 而在這個字串中每一個元素中間相隔的符號就是你傳入mkString的參數, 在上面例子是 comma 逗號

如果在yield 中放的是println 呢? 你該記得的是任何事物在Scala中都是Expression, 並且回傳一個結果, 凡事沒有例外, println也是有回傳值,  println的回傳值是Unit, 就如同JAVA中的void一般, 因此如果放println 在yield中你只會得到一個Unit 的List,
val c = for( f <- a, s <- b) yield println(f + " * " + s + " = " + f*s)
//c : List[Unit] = List((), (), (), (), (), (), (), (), ()..................)

Scala的 for提供了更有活性的應用, 讓你的程式可以簡單的存取各種資料結構中並且運作, 更進一步的應用如Functional data type會在後面詳述

下一章節Scala basic5, Pattern matching

沒有留言:

張貼留言