2011年5月16日 星期一

9. 撰寫 Scala 的 script

Scala 可以用來撰寫 script,此時你不需先宣告 class,就可以直接執行。

script 的寫法,與許多 script language 類似,只是使用 Scala 的語法。
在 Scala script file 中,可以直接宣告 function 與變數,直接執行。
範例
val n = 10
println(n)
上例表示在 Scala script 的檔案內容,只有兩行。Scala interpreter 會直接這兩個 statement。

Scala script 可以使用 args 變數取得外界所傳進來的參數。

Scala interpreter 執行的方式,其實也是先 compile,再執行。
Scala 會把你的 script 變成類似以下的程式碼,然後 compiler,再執行。
object S {
  def main(args: Array[String]) {
    new {
      //原來的 script
    }
  }
}
由於 object 的宣告裡面不允許有 package 宣告,所以你的 script 不可使用 package 宣告。

你可使用 scalac 來 compile script。
但 scalac 需要加上 -Xscript class_name 的 option,否則 scalac 不知道該將 script 編譯在哪個 class 中。

2011年5月5日 星期四

8. Java、Scala、Groovy 合併 compile

1. 使用 groovyc 由 Groovy 原始碼產生對應的 class stubs。

2. 使用 scalac 編譯 Scala 原始碼。需將 Java 原始碼一起餵給 scalac,Java 原始碼在此處的作用,是讓 scalac 參考,知道有哪些 Java classes。scalac 也會將一併參考 groovyc 所產生的 class stubs。

3. 使用 javac compile Java 原始碼。javac 會參考 scalac 所產生的 classes,以及 groovyc 所產生的 class stubs。

4. 使用 groovyc compile Groovy 原始碼,groovyc 將參考 javac 與 scalac 所產生出來的 classes。

7. scalac 的 options

scalac -print:看一下 scalac compile 時產生的大概程式碼

scalac -Xprint:erasure:在 erasure 階段完成後,compile 當時的程式碼

2011年5月2日 星期一

6. Scala 與 Java 之 static method 互相呼叫

1.在 Scala 中如何呼叫 Java 的 static method?

很簡單,與在 Java 中使用的方式一樣,「 class.method(param...) 」就可以呼叫到。
public class J1 {
  static public void static_m1() {
    System.out.println("J1.static_m1...");
  }
}
在 Scala 中的呼叫方式
class S1 {
  def print = {
    println("S1.print...");
    J1.static_m1  // 這裡呼叫 Java 的 static method
  }
}

2.在 Java 中如何呼叫 Scala 的 static method?

Scala 中沒有 static method,取而代之使用 object 來取代。
在 Java 需要使用 static method/field 時,若你改使用 Scala 撰寫,此時你需要 create 另一個同名的 object 來處理原來在 Java 中使用static method/field 的事情。
class S {
  def print_instance = println("S.instance")
}
object S {
  def print_static = println("S.static")
}

其中的 print_static 是原來在 Java 使用 static method 的部份。

那在 Java 中如何呼叫 object S 的 method?

做法為 「 class.method(param...) 」,與呼叫 Java 寫出來的 static method 一模一樣。
class J {
  void p() {
    S.print_static(); // 這裡呼叫 Scala 的 object S 的 method
  }
}

在 Scala 中同名的 class 與 object 會稱為 companion。上例中的 class S 是 object S 的 companion class,object S 是 class S 的 companion object。

object S 編譯之後,會產生一個 S$ 的 JVM class,也就是 Java 看到一個 S$ class。

「那為何 Java 使用 S.print_static(),可以呼叫到 object S 的 method?」

scalac 做了一些手腳,當定義 object 時, scalac 會在對應的 companion class中,插上所定義 object 中的所有 method,並且改為 static。當呼叫 companion class 中的 static method,會 forward 到 companion object 對應的 method。

讓我們看上例 scalac 所產生的 classfile 即可很清楚了解。


S$ 對應到 object S 的部份。



S 為原來 class S 的部份。
由上面可清楚看出, S 包含一個 print_static 的 method,該 method 本來定義在 object S 中,這裡被重新複製一次,並且標明為 static,所以 Java 使用 S.print_static 就可呼叫。

5. Scala 的 Null, null, Nil, Nothing, None, Unit

Null:是所有 AnyRef 的 subclass, null 是該 class 的唯一 instance。所以每個 AnyRef 都可以指定為 null。

Nil:表示為空的 List

Nothing:是所有 Any 的 subclass。角色與 Null相類似,但 Null 對應到 AnyRef,而 Nothing 對應到 Any。Nothing也是 Null 的 subclass。但 Nothing 並沒有 instance。

None:用在 Option class中,為避免像 Java 常產生 null exception,所以引進 Option,None表示此次的 operation沒有得到資料。

Unit:tuple-0的特殊 class,本身是 Value class(並非Reference class),只有一個 instance ()。

2011年5月1日 星期日

4. Loop、Iteration 與 Recursivion

有許多朋友問道,如何寫好「像」函數式的程式。
這裡先澄清,函數式的程式不一定比較好,但寫作完畢函數式的品質通常比較好(比較不會出錯),可是函數式的效率通常比命令式的程式差。

在 Scala 中,兩種方式都有支援,如何取捨端看各人。

Loop 是非常常見的東西,我們從 Loop 來看如何寫「像」函數式的程式。
撰寫重複工作的程式有幾種技巧
1. 使用 Loop
2. 使用 Iteration
3. 使用 Recursion

假設現在我們要解決一個簡單的問題,想要找出一群人中,所有男生的總共歲數。
我們先將需要的資料定義出來
1. class People
2. lst:本範例放入 4 個人
class People(val name: String, val age: Int, val gender: String) {
  def isMale() = gender == "M"
}

val lst = List(new People ("John", 10, "M"), 
               new People("Steven", 20, "M"),
               new People("Kelly", 15, "F"),
               new People("Jeniffer", 18, "F"))

各解法的方式如下:

1. 使用 Loop:直接對每一個people做檢查
def average1(lst: List[People]) = {
    var totalAge = 0
    for (p <- lst) {
        if (p.isMale) {
            totalAge += p.age
        }
    }
    totalAge
}
2. 使用 Iteration:先使用 filter,然後直接運算
def average2(lst: List[People]) = lst.filter(_.isMale).foldLeft(0)((total, people) => total + people.age)
3. 使用 Recursion
def average3(lst: List[People]):Int = lst match {
    case Nil => 0
    case p :: ps => if (p.isMale) p.age + average3(ps) else average3(ps)
}

上述三種作法都可以達到功能。在這三種作法中,看出函數式作法的威力了?

Loop 的作法比較命令式,一個個 element 檢查,若符合則進行下一個 statement。命令式的邏輯是資料讀取、檢查、往下一個 statement以及修改的方式。

Iteration 的作法就比較使用Higher Order Function的作法,完全運行在 List 上,先將不要的 element 剃除掉,然後對整個符合的 list 做一次 foldLeft 運算。原先的問題,轉為「剃除」與「求整體值」,簡單、且清楚。

Recursion 的作法透過一個檢查條件,分為空 List 與非空 List。空 List 當然總共歲數為 0,非空 List則是第一個 element 的歲數,加上剩下的總共歲數。這裡所展現的是非常標準的 List 操作手法,應該也很清楚。

三種作法,你喜歡哪一種? Iteration 與 Recursion 都廣泛運用在 Scala 程式中,建議你要熟悉這些手法。

3. List 中有用的 methods

在 Scala 中 List 是常有用的資料結構,我們可以發現有許多 method 用在處理重複工作時,非常有用。其實這些 method 出現在 Traversable 中(List 繼承 Seq,Seq 繼承 Traversable),只要 繼承自  Traversable 就都可使用這些 method。

這些 method 將 function當成參數傳入,然後各個 element 對該 fucntion做運算,所以熟悉這些 method 也是熟悉 Higher order functionality 的重要基礎。

1. map
  • 傳入一單參數的 function(unary function)。
  • List 每個 element 都呼叫 map 的 function,然後產生與原來 List 相同長度的另一個 List
  • List(a1, a2, ... an) map (f) 會轉化為 List(f(a1), f(a2), ..., f(an))。
  • List(1, 2, 3) map (_ * 2)
    
    上例會得到 List(2, 4, 6)

2.reduceLeft/reduceRight
  • 傳入一個雙參數的 function(binary function)。
  • reduceLeft / reduceRight主要目的為做整合性的運算,將一個 List 的所有element透過運算,轉換成一個 element。
  • 如:找出最大、最小的那個 element。
  • reduce的意思是慢慢將element合併處理。
  • List(a1, a2, ... an) reduceLeft (f) 會轉化為 f(...f(f(a1, a2), a3),...an)。
  • reduceLft / reduceRight 輸入的 function,其 return 值要與 element 的型態相同,且 reduceLft / reduceRight 不像 foldLeft/foldRight 需要有所謂的 initial 的輸入的參數。
  • 若運算之後想得到的值,與 element 的型態不同,無法使用 reduce,需改為 foldLeft / foldRight。比如由一群人當中,相要計算年齡總和,必須使用 fold。
  • List("John", "Mary", "Jason") reduceLeft((s1, s2) =>
        if (s1.length < s2.length) s2 else s1)
    
    上例會得到 "Jason"
  • 但你若想要找到所有字串的總長度,以下的作法(使用reduceLeft)是錯誤。
    List("John", "Mary", "Jason") reduceLeft((s1, s2) => s1.length + s2.length)
    
    上例會得到 type mismatch 錯誤

3. foldLeft / foldRight
  • 傳入一個雙參數的 function(binary function)。
  • 用來將所有的 element 做一次運算。
  • List(a1, a2, ... an) flodLeft (initValue)(f) 會轉化為 f(...f(f(initBalue, a1), a2), a3),...an)。
  • 需要傳入初始值,但 輸入的 function,其 return 值不需與 element 的型態相同。因此可以用來找出各 element 值的總和。
    List("John", "Mary", "Jason").foldLeft(0)((sum, s) => sum + s.length)
    
    會得到 13
  • 可以使用 /: 取代 foldLeft, :\ 取代 foldRight(請注意冒號一定在list那邊)
    foldLeft 格式:(initValue /: list)(f)
    foldRight 格式:(list :\ initValue)(f)

    上例可以改寫為
    (0 /: List("John", "Mary", "Jason"))((sum, s) => sum + s.length)
    

4. exists
  • 傳入一個單參數的 function(unary function),其 return 型態為Boolean
  • exists用來判別是否有符和該條件的 element 存在。
  • 非所有的 element 都會找尋過一遍,只要找到有符合的 element 就會停止。
    List(1, 2, 3).exists(_ >= 2)
    
    上例會 return true

4. partition
  • 用來將 List 分割成兩部份,前面是 ture 的部份,後面是 false 的部份
  • 傳入一個單參數的 function(unary function),其 return 型態為Boolean
  • List(1, 2, 3).partition(_ >= 2)
    
    上例會 return (List(2, 3), List(1))