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))

2011年4月29日 星期五

2. 可以直接使用 scala 的網站

想學習 Scala 最好的方式是 download Scala package,然後解壓縮到目錄中、設定 path 與 classpath,就可以使用 Scala了。

但如果你不想這麼的麻煩,也有一些網站,讓你線上學習 Scala。
在那些網站,你可敲入 Scala 程式碼,然後執行,就像你使用 scala 直譯器環境一樣。

底下有幾個學習網站,有興趣的朋友可以去看看。
  1. http://www.simplyscala.com/
  2. http://scripster.razie.com/
  3. http://tryscala.org/

2011年4月28日 星期四

1. Java 與 Scala 混用

Java與Scala混用,但該如何compile?以及如何執行?

舉例來說:
1. Java class J1
public class J1 {
 public void print() {
  System.out.println("J1.print...");
 }
}

2. Scala class S1 繼承Java class J1
class S1 extends J1 {
  override def print = {
    println("S1.print...");
  }
}
3. Java class J2 繼承 Scala class S1
public class J2 extends S1 {
  @Override
  public void print() {
    System.out.println("J2.print...");
  }
}
4. Scala class S2 繼承 Scala class J2
class S2 extends J2 {
 override def print = {
  println("S2.print...");
 }
}

5.測試class
public class Test {
 static public void main(String[] args) {
  new J1().print();
  new J2().print();
  new S1().print();
  new S2().print();
 }
}

繼承的方式 J1 <- S1 <- J2 <- S2






















先compile Java程式或先compile Scala程式,好像都不對,因為有cross reference的關係。

其實,scala會parsing Java source code,會預先知道之後會產生的javac會產生的class,運用這種方式,我們可以使用的解決方式如下:

1.使用scalac compile Scala code:此時需要將 java的source code加進去,讓scalac知道有這些 java source code。此步驟會產生所有Scala的classes(雖然有繼承自Java的scala class,此時仍會產生)




會產生以下的classes(注意:此時J1.class, J2.class都尚未產生)












2.使用 javac compile java source code(注意此時需要將scala-library.jar放入classpath中)



















沒有將scala-library.jar放入classpath中,會產生以下的錯誤






3.執行:使用 java 執行,scalac所compile出來的classfile與javac 所產生的classfile是相同的,我們可以把Scala class當成一般正常的 Java class。

注意:在執行時,同樣需要將 scala-library.jar 要放入classpath中