2011年4月23日 星期六

3-4. Local Variable 宣告

var / val 除了可用來宣告 data field,也可用來宣告 local variable。
Local variable 的宣告方式與在 Java 的相同,不同的 scope 可以有相同名稱的變數。

以下為不同 scope 中,相同名稱的變數的宣告方式。
class S {
  var n: Int = 0 // data member
  override def print = {
    var n = 10 // method print() 的 local variable
    for (i <- 1 to 10) {
      var n = 100 // loop 裡面的 local variable
      println("i + n = " + (i + n))
    }
  }
}

3-3. 變數宣告與重新設定值

Scala 與 Java 相同,都是強型態的語言,而且沒有宣告變數,不能使用該變數。

宣告變數,Java 使用的格式是
    var_name: type;
    記得分號 「;」 是 Java statement 的分隔符號,不可省略。

例:
n1: int;

在 Scala 中宣告一個變數,格式為
    var var_name: type;

var 是 Scala 的 keyword,表示要開始宣告變數,後面接變數名稱,再接 type。type 後面的分號,可以省略。
例:
var n1: Int = 10

Scala 在這裡有許多與 Java 不同的地方:

A.分號:

在 Scala 中,為符合快速開發的原則,可以省略的,都省略了,所以10後面的分號是可以省略的。省略分號的原則,四處都適用,但你若不喜歡省略,也是可以繼續使用。

B.初始值:

在 Java 變數的宣告,初始值是可有可無的,這可分為兩種狀況:

1. 當宣告的變數是 method 裡面的 local variable 時,Java 不會強迫你一定要指定初始值。但若你尚未指定值,就使用到該變數,compiler 不會允許這種情況,會在你使用該 variable 的地方產生 error,要求你在變數被使用之前設定該變數值。

2. 當宣告的變數是 class 中的 data field 時,每個 data field 都需要有初始值。只是你若沒有特別指定,javac 會幫你加上 default 的初始值,這些初始值如下:
  • 數值型態的初始值為 0 或 0.0
  • 布林型態的初始值為 false
  • Reference型態(在Java中所有的 object 都是 Reference 型態)為 null
Scala 中變數的宣告,初始值是必須要的,不管是  local variable 或是 data field,每個變數宣告一定要設定初始值。但你若懶得直接寫值,Scala 也支援使用預定初始值。

所謂的預定初始值,是使用系統預定的初始值,這有點像 Java 對待 class 中的 data field 一樣。但在 Scala 中,你可要講清楚要使用系統預定的初始值,作法如下:
var n1: Int = _
var s1: String = _

那個 underscore _  是 Scala 的「預定萬用字元」,代表的意思就是那個「預定的東西」。
這個預定萬用字元在 Scala 運用的範圍非常廣泛,你以後會很容易看到,我們在其他遇到的地方適時解釋給各位瞭解。

預定萬用字元使用在初始值,所代表的意義就是使用系統的預定值。
系統的預定值依據不同的型態會有不同的預定值,與 Java 類似,數值型態預定值為 0、布林型態預定值為 false、其他的 Reference 型態為 null。(在 Scala中所有的東西都是object,所以這裡特別要說明是 Reference 型態,不可說所有的 object 都會初始為 null)

上述兩個宣告
var n1: Int = _  // 與 var n1: Int = 0 相同意義
var s1: String = _ // 與 var s1: String = null 相同意義

C.var 與 val:變數與常數

在一般的語言中,我們可以看到有兩種變數,一種真正的變數(可以重新設定值),一種是常數(不可重新設定值)。在 Scala 中支援這兩種,變數使用 var 宣告,常數使用 val 宣告,var 是 variable 的縮寫,val 是 value 的縮寫。

1.var
var n1: Int = 10
2.val
val n1: Int = 10
val宣告的常數指定初始值之後,不可以再被重新設定(就算設定的值一模一樣也不可以)。

Scala 宣告 val 的常數,被 compile 出來之後就是一個加上 final modifier 的 variable,也就是在任何地方,該變數都不可再被重新設定值,不論你使用 Scala 或 Java 來存取該常數。

D. 型態推論

型態推論簡單講是,由於 compiler 可以判斷等號的右邊是一個 Int 型態,所以 n1 沒有特別指明哪個型態時,就由 compiler 來自行做型態的推論。型態推論是一個重要的觀念,我們將在以後篇幅中討論。
var n1 = 10
以上例而言,n1 被指定為 Int 型態。

Scala 最嚴謹的變數宣告是「變數名稱加型態名稱,後面再加上初始值」,但由於分號省略與型態推論,所以可將變數宣告寫的更精簡
如:
var n1: Int = 10;

1.分號省略:可以變為
var n1: Int = 10

2.型態推論(Type Inference):可以變為
var n1 = 10

「var n1 = 10」 這種宣告方式,反而是最常見的變數宣告方式。

重新設定值:變數宣告完後,你可以重新給它新的值,此時的用法與 Java一模一樣,但 val 常數不可以在重新 assign 值。
var n = 10 //宣告變數,並給初值
n = 20 // 重新 assign 新的值
請注意:n = 20 這個 assignment 的 result value 是 unit value(unit value 以後會討論,現在先把 unit value 看成沒有值),不是 20 這個值。

3-2. 分號省略原則 -- 分號推論

Scala 為了讓程式開發可以加速,以及可以運用在 script 的寫作上面,對於分號「;」的處理原則是能省則省。

分號是 C 語言系列的標準分隔符號,一個 statement 的結束必須以分號為準,並不參考其他的東西(比如分行),這個原則其實很久就受到多方的挑戰,尤其是 script 語言。

我們常見的 script 語言除可使用分號當分隔符號外,大都也可使用分行當分隔符號(如:bash、DOS 的 bat 等)。基於 script 語言強調「簡易、方便」的處理原則,只要想要切入 script 應用的語言,都會考慮分行作為分隔的原則。

例如 JavaScript,他的語法屬於 C 系列(Java 承繼 C 的語法精神、JavaScript 承繼 Java 的語法精神),但 JavaScript 決定分號並不是唯一的分隔符號。在 JavaScript 中你仍然可以使用分號來結束一個 statement ,但只要在允許的範圍內,分號是可有可無的。

何謂允許的範圍?就是「JavaScript Engine 如果可以分辨出一個獨立的 statement」的狀況下,這種狀況最典型的就是分行,因此你若有分行分開兩個 statement,那個分號就已變成可有可無,沒有存在的必要,這時的分號是可以省略的。

Scala 的思想邏輯也是著重在簡單上,希望讓 Programmer 快速開發程式,因此自然有相同省略分號的思考邏輯。

在 Scala 中,只要可以省略分號的地方,你就可以省略,這就是「分號省略原則」。

當然,你若執意要保留分號,以便與 Java 程式碼看起來比較相像,也是可以,只是分號省略是 Scala 比較推薦的方式。

「什麼是只要可以省略分號的地方?」
大部分的 programmer 在一行只會寫下一個 statement,不同的 statement 寫在不同行是絕大多數人的習慣,所以 statement 與 statement 中間通常都會有分行符號,因此預測一個分行代表該行的 statement 的結束是合理的推論。依照這個推論,分行符號通常隱含著一個分號,所以 Scala 把分行當成可以省略分號的地方。

正常情況,Scala 會在行與行之前,偷偷加上分號,所以該行的 statement 會被結束。但下列情況的分行符號,Scala 不會進行偷偷加上分號的動作
1. 該行的命令尚未結束,比如在一個 infix operator 的 statement 中間,或是呼叫某個 object 的 method,但只寫到點「.」這個符號,此時不會自動補上分號。
2. 下一行的 statement 並不是一個正常 statement 的開頭,此時也不會自動在前一行補上分號。
3. 小括號與中括號內部的分行,也不會自動補上分號。

「除以上三點的情況,在一般分行符號加上分號」,這種動作就稱為分號推論「semicolon inference」。

我們來看以下的例子:
var n1: Int = 10;
var n2: Int = 20;

我們可以改寫為
var n1: Int = 10
var n2: Int = 20

有沒有分號,兩者所代表的意義,都一模一樣。

3-1. class 宣告

從此篇開始,我們進入 Scala 每個組成分子的實際介紹,請有興趣的朋友跟著我們的程式做。

這篇要談的就是 class 的宣告。啊?太快?太硬?

之前文章曾經提過,Scala 中所有的東西都是 object,所以 class 是它最本的結構。
在 Java 程式中,所有的東西都要定義在 class 中,Scala 也是同樣的情況,所有的東西都要定義在 class 中。因此我們決定從最基礎的 class 談起。

讓我們先看一下 Java 中 class 的宣告方式
class J1 {
  int n1;
  void p1(int x) {
    System.out.println("p1:" + x);
  }
}

上例宣告一個 class J1
J1 裡面有一個 data field 叫 n1
J1 裡面有一個 method 叫 p1
p1 的 return type 為 void(就是沒有return值)
p1 有一個 int 型態的參數x

Scala 的 class 宣告方式與 Java 宣告在含意上非常像,我們先看例子
class S1 {
  var n1: Int = 10
  def p1(x: Int): Unit = {
    Console.println("p1:" + x)
  }
}
上例宣告一個 class S1
S1 裡面有一個 data field 叫 n1,起始值為10
S1 裡面有一個 method 叫 p1
p1 的 return type,為 Unit(Unit後面會討論到)
p1 有一個 Int 型態的參數 x


雖然 Scala 的 class 宣告方式與 Java 不太一樣,但精神卻是一致的。
讓我們一個個來看
1.宣告 class 的方式,在 Scala 中與 Java 一樣都是使用 class 這個 key word.
2.宣告變數的方式有更改, 使用 var 宣告
3.宣告 method 的方式有更改,使用 def 宣告

接下來看繼承的方式

Java 的繼承方式
class J2 extends J1 {
  void p2() {
    System.out.println("p2");
  }
}

Scala 的繼承方式
class S2 extends S2 {
  def p2(): Unit = {
    Console.println("p2")
  }
}
Scala 繼承方式與 Java 很像,都是使用 extends 這個 key word。

有了宣告 class 與繼承的方式,那麼 implements interface 的部份呢?

interface 的觀念在 Scala 中做了大幅度的修正,觀念也不同了,這部份比較複雜,我們以後的篇幅,再詳論 Java 的 interface 在 Scala 如何修正的。

以下幾篇我們將詳論 Scala 在 class 宣告中各個組成份子。請記得是 Scala 的 class 宣告與繼承與 Java 的方式非常類似,都是使用 class 與 extends,但 interface 的部份,Scala 已經大幅修改。

2-7. Scala 經典學習

Scala 範圍廣大,沒有方向是不易學習的,在 Scala 世界中,有幾個地方你不宜錯過,以下簡單介紹

1. Scala 大本營的說明:位在此http://www.scala-lang.org/node/274,當你使用 Scala 的開發過程有不熟悉的地方,可以參閱此處的相關說明。

2. Programming in Scala:一本好書,Martin Odersky (Scala設計者) 與 Bill Venners (Inside the Java Virtual Machine 作者) 共同撰寫,已經有第二版。



3.Programming Scala:另一本好書,最好的是可以線上閱讀。請參閱 http://programming-scala.labs.oreilly.com/index.html

Cover for Programming Scala

以上,是你學習 Scala 的好地方,其他的學習就是線上演練,實際寫些程式,試著做。

你若熟悉 Java,慢慢你會發現,Scala 沒有那麼難懂,瞭解它後,你會覺得,其實它還蠻可愛的!

Scala 的介紹到此告一段落,接下來將會實際介紹 Scala 的各個組成分子。
同樣的,我們鎖定你是 Java 的開發者,我們會以 Java 的角度來解釋 Scala 的各個組成分子,請繫緊安全帶,準備出發。

2-6. 其它的Scala工具或Library

除了之前介紹的 Lif t 與 Akka, Scala 也有許多好用的 library,Scala 主站也蒐集一些,大家可以參考

1. Contributed Libraries and Tools:http://www.scala-lang.org/node/1209
2.Scala Bazaar(或稱sbaz):http://www.scala-lang.org/node/93

2-5. 轉移到Scala,有風險嗎?

學習一個新語言,除了該語言的功能與易學性需要考慮以外,對於該語言各種應用的解決方案,以及如何延續自己既有的程式資產,也都應該列入考量的範圍。

我們發現有很多新的語言標榜很多功能,但對於解決方案的支援卻很缺乏,這時使用者是否要轉而接受該語言,就有待考驗。

同樣的情況,也出現在 Scala 上,Scala 就算再強,也不可能解決所有的問題,我們真的可以放心轉移到 Scala 嗎?Web 怎麼辦?Database 怎麼辦?

這些問題在 Scala 世界中,可能不會那個嚴重。原因是因為 Scala 可以繼承 Java 廣大的解決方案。

前面提過 Scala 與 Java 彼此可以混用,只要是 Java 的 library、package 或 framework,Scala 都可以使用,無形中,Scala 已有成堆的解決方案。可以使用 Java 的資產,也是 Scala 設計時重要的考量因素之一。

在同一團隊中,使用 Scala 與 Java 寫作出來的程式碼,因為可以互相混用,因此也少了移轉障礙,這對進入 Scala 世界少了很多的障礙。盡量繼續使用你原有 Java 的資產,不會因為進入Scala 世界而要完全放棄過去的投資,只是你以後要有使用兩種語言寫程式的心理打算,合適 Scala 的時候使用 Scala,合適 Java 的時候使用 Java。

但這是否代表在 Scala 世界中,不提供一些常見的解決方案?
當然不是這樣,在 Scala 世界中也有許多很棒的設計,以下簡單說明:

一.Web 解決方案

Web是現在最主流的開發方式,不管 Windows、Java、PHP、Ruby 都有一大堆人投入其中。Ruby 這個語言本來默默無聞,但藉著 Ruby on Rail 名震天下,有許多所謂 Web 2.0 的網站是用 ROR 所寫作的。ROR 的成功也讓 Java 意識到自己的老舊與繁瑣,而有許多改進聲音出現(如:closure、continuation 都是那時要求改進的項目)。

Java 世界中,Web 的解決方案更是多元,有 Servlet、JSP、JSF、各式的 framework(Wicket、Struct、Tapestry、Spring)、各種 template(Velocity、FreeMaker)、Groovy 的 GSP、Grails等。看到這,你應該累了,Java 應該是有史以來產生最多 framework 的語言。

在 Scala 中,有一個非常有名的 Web 解決方案,名叫 Lift,設計者 David 在瞭解 ROR 以及 Wicket 後,所設計出來給 Scala 世界的一個很好的 Web 解決方案,非常有名。
很有趣的是,David 現在想要將這個解決方案,推銷到 Ruby 世界中,希望 Ruby 的使用者可以透過 JRuby 來使用 Lift,可見得設計者對這個產品的信心。

Lift 這個東西很有用,若你希望使用 Scala 撰寫 Web AP,你應該不可忽視它。這麼重要的軟體,未來我們有機會將討論它。

二.平行處理

Scala 對於平行處理著墨甚多,可見得平行處理本身的困難度有多大。

在平行處理的問題上,除 Scala 本身提供的 Actor Model 外,在 Scala 世界中還有一個重量級的解決方案,叫 Akka。

Akka 提供的功能超出 Scala 甚多,包含更精細的 Actor、STM(Software Transaction Memory,把你在 memory 的資料看成和 database 一樣,也有 transaction 的功能) 等,都是解決平行處理問題的關鍵技術。

深受 multi-thread 困擾的朋友,Akka 會是你解決問題的好幫手,這麼重要的東西,未來我們也會討論。對了,Akka 也支援使用 Java 使用(不需透過 Scala),趕快去看看吧。