2011年4月28日 星期四

21. Scala 的企圖心,也是它的特性 --DSL

DSL 是 Domain Specific Languages 的縮寫,它指的是特殊領域的特殊語言。

「為何需要DSL?」

其實每個領域中,都有一些特殊的語言,使用特殊語言操控該領域的知識,會比較得心應手,比較直覺。
比如 game 或是 database,都有特殊的語言。SQL就是 database 的特殊語言。
以往如果要設計某個領域的特殊語言,那可要大費周章,從 parser 到 compiler 或 interpreter,很累人。

在 Scala 中,憑藉 Scala 的某些特殊語法,讓你很容易兜出個別領域的特殊語言,你可以使用這個特殊語言來進行你的工作,很妙吧!

其實,Scala 的 DSL 只是一個看起來很像該領域的語言。內在的運行方式還都是 Scala 原本的運行方式,你只要清楚 Scala 的運行方式,很容易設計不同的 DSL。但注意,這還是個假象,讓你使用起來比較容易專注在該領域中,其實內裡還是 Scala 的運作模式。

這樣的 DSL 是「Scala為內裡,DSL為外表」的寫作方式,認真說,還是遠遠不及重新設計一個專用的語言來的方便與正確。但話說回來,我們一定需要專用的語言嗎?大部分的情況,Scala 所表現出來的 DSL 已經非常夠用。 設計 DSL 的簡易性,也因此被 Scala 拿出來大肆宣傳。

20. Scala 的企圖心,也是它的特性 --Pattern Matching

在 Java 中你應該使用過 switch 這個命令,但只能比較整數類型,Java 7 會要支援 String 的比較,讓 switch 命令更加好用。但比起 Scala 的 Pattern Match,就算小巫見大巫。

Pattern match 的來源,絕對不是 switch,它其實是出自於 functional language 的特性。
回想我們寫數學函數時,常會遇到「若參數是什麼就如何,不是的話又是如何」,這引導出pattern match。

以下以費伯納西數列舉例,什麼是費伯納西數列,簡單的講就是後面的值等於前兩個值的相加,但第一個值是1,第二個值也是1。
所以費伯納西數列:1, 1, 2, 3, 5, 8, 13, ...

如何製作費伯納西數列?你在一些函數式語言中,可能會有這樣的寫法(此為虛擬碼,不要問我這是什麼語言)

function fib(1) = 1
function fib(2) = 1
function fib(n) = fib(n - 1) + fib(n-2)

第一行指令,說明fib(費伯納西)這個函數,參數 1 時,值為 1
第二行指令,說明fib(費伯納西)這個函數,參數 2 時,值為 1
第三行指令,說明fib(費伯納西)這個函數,參數 n 時,值為前面兩個值的相加

請注意!這三個指令已經將要做的事做完了,不需在那裡 loop 。
這就是函數式語言的概念,是不是很像前面用口語敘述的方式。

「這跟 pattern match 有什麼關係?」
在前面函數式語言的三個 fib 定義中,我們個別定義參數是1, 2,  n時  fib 應該如何做。
當執行時,呼叫到 fib 的函數,那時會有一個真實參數,函數式語言將會使用真實參數,逐一與各 fib 定義中的參數比對,找出符合的 fib 定義,找到後執行該 fib。
使用真實參數,逐一比較各個 function 的參數,這就是 pattern match。

「等等,這不還是與 switch 功用相類似嗎?有多稀奇?我用 switch 也可以做得到!」

嗯嗯,確實,費伯納西的參數是整數,難怪可以使用 switch 取代。
那再來另外一個例子,假設你要寫一個 greeting 的功能
1. 對於你的好友 John 要說 "John 你好"
2. 對於女士要說 "Miss " + name + " 你好"
3. 對於男士要說 "Mr " + name + " 你好"
4. 對於鴨子要說 "呱呱"

你該怎麼做?準備 「if 來 if 去」來個 if 大作戰了嗎?

看看函數式語言如何做!
function greeting("John") = "John 你好"
function greeting(isMiss(name)) = "Miss " + name + " 你好"
function greeting(isMr(name)) = "Mr " + name + " 你好"
function greeting(duck: Duck) = "呱呱"
乾淨俐落!

上面的例子也是使用參數比對來製作。從這個例子,你可看出函數式語言的參數比對,可以非常的複雜。不僅比對值,還可以比對 List,比對型態,運用非常廣泛,絕非 switch 這麼簡單的命令可比擬。

看到此,你不需急著去學習函數式語言,Scala 的 pattern match 也可做到相類似的功能,同樣可以比對值、比對 List、比對型態,也因此 pattern match 才會被列為 Scala 的超強特性。

對於 pattern match,這裡不會深入介紹,這裡只是讓大家知道有這一回事。

順帶一提 Scala 中只有 pattern match 並沒有 switch,不要誤用了。

19. Scala 的企圖心,也是它的特性 --XML處理

XML 前幾年風行極了,好像什麼東西都要與 XML 扯上一點關係才可以。

應用程式的 configuration 檔要使用 XML 的格式(實在滿坑滿谷,最簡單的就是你的 Apache Tomcat 的 web設定檔),資料交換也要使用 XML(SOAP 就是這樣來的,交換的格式就是XML),聽說本來 Java 語言的演進中,也要將 XML 處理直接放在 Java 語言中處理,雖然到現在還沒看到蹤影。

XML 筆者個人沒有特殊的好惡,但搞到沒有使用 XML 就好像不對,筆者就覺得有點走火入魔了。其實 XML 主要是給機器看的,硬要給人看,實在是有點過份、有點累。

你喜歡 Servlet 的 web.xml 設定方法?筆者是不習慣的,還好 web.xml 沒有幾層,否則光對齊 tag 就暈了。

有許多東西是好的,但要強迫用到不合適的地方就過份,相信你有看到過有十幾層以上的 XML 檔。如果那只是用在機器間的交換,那還OK,但如果那個 XML 是要讓人寫出來或是讓人讀的,那就叫整人。

這種 XML 走火入魔的方式本來也影響到 web client 與 web server 的資料交換,鼎鼎有名的Ajax,那個「x」代表的就是 XML,意思是 web client與 server 間使用 XML 來交換。

很多 programmer 看久了這種 XML 不免眼花撩亂(誰說 web client 與 server 間的資料人不用看,很多時候 programmer 可是要看它個千百回!),這時就可證明 XML 實在不適合人來看。

慢慢有人厭倦了,既有人厭倦,就有人會提出不同的作法,JSON(JavaScript Object Notation)因此現出江湖。
相對於 XML,筆者更愛 JSON,現在正常的人應該不會使用 XML 在 Ajax 的呼叫中了吧(Ajax 要改個名叫 Ajaj 嗎?!)。

啊?你現在還在使用XML?也許該順應一下潮流換換囉。現在,可能只剩下SOAP這種老古董才會堅持一定要用那麼複雜的XML格式。

JSON 的例子說明「不要以為是好東西,就要給它用的舖天蓋地」,但 XML 可也不是壞東西,大家可不要誤筆者的意思,筆者的意思是合適的地方用,不合適的地方不要用,給人看的地方,就是不合適 XML 的地方,至於合適 XML的地方,就是用在機器與機器交換的地方。

既然 XML 還是個好東西,就有需要讓它變得更好用,在 Java 中有許多關於 XML 處理的package,這裡不多談。

Scala 比 Java 更進一步,直接在語言中支援 XML,讓 XML 就像整數與字串一樣,直接在語言就處理。

「什麼是語言就直接支援 XML?」

看以下的簡單範例可以清楚點






1. 第一個指令直接使用 XML 資料,直接接指定給變數 x
    注意:XML 資料並沒有使用字串括起來,這代表 XML 是 Scala 原生支援,稱為 XML literal

2. 第二個指令抓取 x 中 "b" tag 的資料

3.第三個指令抓取 x 中 "a" tag 的資料

對於處理 XML 這樣是否非常方便,直接在語言層次支援 XML 是 Scala 的一項大特色。

18. Scala 與新的語言功能 --快速開發安全的應用程式

快速開發安全的應用程式,就是讓 programmer 可以很快做完他的工作,而且寫出來的程式碼不容易有 bug。

「寫出 bug 的程式不是 programmer 的問題嗎?」

一方面是,一方面也不是,若是環境面可以阻擋 programmer 不容易寫出壞程式碼,那不就很好嗎?
像static type與 dynamic type之爭,static type 就是可以讓一些比較粗心的 pprogrammer 不會寫出錯誤的程式碼。
另外,如 Java 移除掉 C 語言中的 pointer,與增加 garbage  ollection,也是因為這兩個特性可以大幅增加程式碼的可靠性。

Scala 有什麼方式可以增加開發速度與程式可靠性呢?

1.Scala 增加 functional 的特性:
  • 之前提到,函數式的方式著重在函數的可重複性,所以函數可以重複執行,不因情況不同而有不同的 return 值。
  • 這種特性在平行處理中非常的重要,某個函數早執行晚執行都不會讓程式有不同的情況發生,這就讓 programmer 少了仔細的需求,減少 programmer 犯錯的機會。
  • 這裡面的重點,就是多用函數,多用不可變(immutable)的資料結構(你若熟Java,就知道 String 就是不可變的資料結構),多用常數(一旦指定了值就不可再變的變數)。為何要多用不可變的呢,就是避免平行處理中的競爭問題(race condition)。
2.盡量支援平行處理:
  • Scala 想要解決平行處理的問題,希望讓我們不用太花腦筋,就可以寫出 multi-thread 的正確程式。
  • 有經驗的 programmer 看到 multi-thread 也要倒抽一口冷氣,真難,頭腦不轉清楚,一不小心,不是無法同時進行,不然就是 deadlock。
  • Scala使用 actor 的模式來解決平行處理。筆者認為,其實平行問題的範圍廣大,資料共用是最麻煩問題,actor 模式也只處理掉一點點,雖然已大幅改善寫作品質,但面對平行問題,你還是要多小心。
3.Type system:
  • Scala 的型態系統是有名的複雜,但透過 type system,compiler 會預先幫我們過濾可能的問題,預先提醒。
4.Generic(泛型):
  • Java 的 generic也就是 C++ 的 Template,就是讓一段程式碼可以適應各種不同的型態,不用因為不同的資料型態而寫出很多很像的程式碼。
  • Scala 承繼 Java 中 generic 的觀念,並且把它擴充的更完整,更嚴格,所以也更不容易寫出錯誤的程式碼,保障程式碼品質,但同樣的就更複雜,這是需要付出的代價。

17. Scala 與新的語言功能 --Meta Programming

Meta programming 這種技巧,在一些新語言上非常強調。
所謂 meta(翻成中介),其實就是以「抽象這一層」來作為處理的對象。

所謂抽象這一層,就是抽取出一層來。舉例而言,object  (或稱  instance)  的抽象那一層就叫 class。在 meta programming 中要強調的是如何處理抽象這一層。

讓我們將話講更實際一點,在一般語言中所謂的 meta programming,就是如何用程式定義(或控制) class 的行為,比如增加 class 的 field 與 method,或是用程式定義「當有程式呼叫到 class 沒有的 method 時應該如何處理」的手段。

在一些語言中,你可以動態的增加某些 class 的 method,也可以動態改變某些 class 的 method 或field,也可一次攔截該 class 中的所有 method 呼叫。甚至可以設定某些 class,若呼叫到該 class 不存在的 method,或存取到該 class 不存在的欄位,該如何處理。這都是 meta programming。

雖然 meta programming 有它的優點,但也有它的缺點。

第一個缺點就是 compiler 無法幫你做預先的篩選。比如,你指定了呼叫到不存在的 method 該處理的方式,若你真的打錯 method 名稱,此時的 compiler 能力幫你檢查。

第二個缺點就是效能,既然需要動態決定這麼多事,需要預留太多的空間,因此 compiler 勢必無法有效產出好的 bytecode,這樣一定會影響效能。

Scala 在這方面搭支援到現在是比較缺乏,現在只看到一個 Dynamic 的 class 有點類似,但還不清楚,未來我們將持續觀察。

16. Scala 與新的語言功能 --Continuation

Continuation(延續性)是可以讓程式暫時停止,然後在有需要的時候,再繼續執行。這是一個非常有用的特性,尤其運用在 web 程式的開發。

在 web 程式開發,通常一個功能需要使用者好幾次使用網頁輸入,程式執行中間需要等待使用者的回應。
我們舉一個簡單案例,在 web 製作假單申請這個功能,我們的程式流程可能定義如下(假設使用者已正常登入)

1.畫面顯示假別供使用者選擇想要申請的假別
2.畫面顯示請使用者輸入假期的區間
3.畫面回應使用者申請成功與否

這是一個非常簡單的 web 應用,但實做卻顯得繁瑣。
現在常見的作法是由畫面串連到下一個畫面。
第一個畫面輸入完畢之後,由第一個畫面串連到第二個畫面上,第二個畫面再串到第三個畫面。你的程式是否也是這樣設計?

這樣的解決方案,最麻煩的地方是流程稍微有點更動,就需要找到畫面程式加以修正,對於以後的擴充與維護造成相當大的困擾。

「那是否有比較好的方式?」

有!就是這裡所提的 continuation。

以上例而言,我們可以有一個流程程式,當流程程式執行時,先丟出第一個畫面給用戶,這時流程程式進入等待階段,等待用戶的輸入。
等待階段並不會浪費任何的 thread 與程式資源。當用戶寫完第二個畫面的資料後,會再重新啟動流程程式繼續執行,此時流程程式會依照流程的定義帶出第三個畫面給用戶輸入,依此進行。

使用 continuation 機制製作的程式,控制 web 畫面的是那個流程程式,所以畫面與畫面將不會有串連的問題,未來要修正畫面或修正流程將會非常方便。

另一個 continuation 常用的地方是 network 程式開發,網路程式通常也需要等待對方的回應,才會進行下一步的動作,所以很適合使用 continuation。

對於網路程式的開發,為讓 server 端的程式可以服務多個 client,開發方式有以下的演進

1.最早的處理方式是當有新 client 連接進來時,就 fork 出一個新的 process 來處理該 client 相關的事情,這時這個 process 只處理一個 client,程式可以使用 sequential 的方式處理,不會有問題。
這種模式稱為Process-Based的Server,早期的 Apache Server 主要的處理方式就是採取這種模式。

2.由於啟動一個 process(這是 OS 的 process),所以 server 開銷相當大,以至於 server 無法負擔太多的 client,因此修正為在同一個 process 中啟動不同的 thread 來處理個別的 client。
早期的Java network程式,這是最主要的處理方式。

3.在整臺機器中,thread 終究還是有限制的資源,為應付更多的 client,event-based 的處理模式被引進 Java 中。在 Java 我們常看到的 nio package 就是這種。nio代表 non-block IO,是當網路有資料要讀或要送時,才會回頭呼叫我們的程式,現在 Java 已經進步到 nio2。

nio模式的困難點在於你很難寫一個以 sequential 邏輯為主體的網路程式。
programmer 被迫要一次處理所有的 client,眼光就是一次處理所有的 client,需要將原來網路程式的各個步驟紀錄在不同的資料中,然後依照不同的 client 適時呼叫不同的模組,很麻煩。

為何我們不能像以前開發單一 client 的方式一樣,將處理的眼光只侷限在一個 client 就好。這就是 continuation 運用在 network 程式的方式。
當 nio callback 到我們的程式時,我們只需要該 client 所對應到的 continuation 程式叫起,然後要求該 continuation 程式繼續執行,就完成了。

以這種概念開發程式最重要的地方在於 continuation 機制的完整與否。

Continuation 可以讓我們製作一些程式時很簡單,但簡單的前提是要有好的 continuation 機制。很可惜的,在 Java 世界中現在沒有比較好 continuation 機制。
Java 世界確實存在一些 continuation 機制,但這些機制不是設定方式過於複雜就是實用性不好。有一些 web framework 號稱有 flow 或是 web flow 的機制,讓人燃起一些希望,但仔細瞭解後卻感到實用性不大。

嚴謹來說,在 Java 世界,根本沒有完整的 continuation 機制,最多只是半成品,對於有這麼龐大資源、framework 滿坑滿谷的 Java 世界,實在讓人訝異。

Continuation(延續性)很早就出現在 Smalltalk 中。由於 web 應用的特性,所以有幾個 web framework。相繼支援continuation,比如 Ruby On Rail (ROR)。

在幾年前 ROR 贏得眾人眼光之際,有人提出 Java 支援 continuation 的需求,比如有名的 Jetty Web Server 非常努力爭取。那時的 Java 團隊壓力不小,也經過許多次會議,但最後決議當然是「不支援」。

經過前篇 closure 的討論之後,我相信各位讀者都很清楚,要在 Java 這個老舊的機器加上其他強大的功能不是太容易,所以這個不支援 continuation 的決議,應該也不會太令人震驚。

各位讀者,可能會認為進到  Scala 這個世界,continuation 就自然垂手可得,但很不幸,這次你要有點失望了。
沒有說失望是因為 Scala 確實有支援,但有點失望是因為 Scala 支援 continuation 的方式令人有點不敢恭維。

Scala 的 continuation 方式有它的學理根據,它使用 CPS (Continuation Passing Style) 的方式來製作 continuation,Scala 又稱呼這種模式的 continuation 叫組合式的 continuation (Composable continuation)或 delimited continuation(意思為:一個continuation是各個段落所組成起來的)。

CPS 的意思簡單來說,就是「執行一段程式時,執行到某個地方,暫停後,將剩下的程式碼移轉出去。這個移轉出去的程式碼,在下次需要時,再將它啟動繼續執行」。
這個「暫停後,將剩下程式移轉,之後繼續執行剩下的這段程式碼」,就很像是一般我們對 continuation 的認知,可達到我們的需求。

這種 CPS 的技巧常見於函數式語言中,由於函數式語言中的函數,可以是函數組成函數,所以很容易使用函數的組合來製作這種組合式的 continuation。

Scala 基本上完全遵循函數式語言的這種模式,因此可達到 continuation 的需求。Scala 使用Responder 這個 class 輔助你撰寫 continuation,看看以下的例子:

object Test2 extends Application {
def sumup(x: int): Responder[int] = 
    for {
      val line <- ask("enter a number");
      val rest <- if (line == "") constant(x) 
                  else if (line == "?") { println("sum so far: " + x); sumup(x) }
                  else sumup(x + Integer.parseInt(line))
    } yield rest
  println("sum = " + run(sumup(0)).get)
}

for 裡面的程式碼是 continuation 的部份,可以讓你暫停程式,讀取資料後,再進行。
要注意的是,這裡的 for 並不是一般 Scala 看到 iteration 的意思,實在是令人驚訝的使用方式。

Scala 的 continuation,學理很精妙,但實用起來,國外有人說會讓人頭腦炸掉。筆者認為講得不無道理。很難要求每個人的頭腦都像 Martin 等人都有大師級的能耐。

若是你有一些專案需要使用 continuation,除非你是大師級的人物,或是很有毅力且有足夠時間的人,那你可以嘗試 Scala 的continuation。至於其他,也許再等等看以後的機會。

若你的專案很趕,那筆者建議你用原來處理流程的方式來處理你的問題(畫面串畫面不是好事,但也可以),不要過於寄望 Scala 的 continuation。

參考資料

15. Scala 與新的語言功能 --Function 與 Closure

在 imperatvie 世界中,function 與 closure 是一個很特殊的議題,尤其在 OO 的程式語言中,函數更是一個很大的衝擊。

這裡講的函數不是一般的 method,而是把函數當成一等公民,也就是 function 是 first-class 公民。

所謂一等公民是把 function 當成與資料一樣,可以設定,可以呼叫,可以傳遞,一般資料可以處理方法,function 也都要可以。

純 OO 的語言不願意接受非 object 的東西,object 通常代表是一個東西,容納資料(或叫有狀態)與行為的東西,有時,甚至資料(狀態)的含意更為強烈一些。

Function 很顯然是用來「描述行為」的,與 OO 物件「有資料有行為」的本質,兩者格格不入。有些 pure-OO 的擁護者,一看到 function,先拒絕再說。

「什麼是closure?」

Closure有人翻作閉包,closure簡單講就是一未命名的函數 (anonymous function),再加上他所閉鎖住的變數。什麼是閉鎖?有點困難是不是,沒關係,這裡暫時把 closure 當成一個沒有名稱的函數。

「Closure有什麼用處?」

用處可是很大,我們常常在 event handling 中需要一個 function 來處理該 event。

這裡舉個例子,若我們的 AP 想要攔截 mouse click,我們就需要一個 mouse click 的 event handler,最方便的作法是撰寫一個「event 的處理 function」,然後將該 function 設定給 mouse click,當用戶真的 mouse click,就直接呼叫該 function。

這是很簡單直覺的想法,但這種想法其實已經把 function 當成第一等公民來看待。

在 C 裡面可以使用 function pointer,在一些  script 語言更方便,直接指定一個 closure 給 event handler 即可。

在一些 pure-OO 語言呢?可就沒那麼簡單了,該 function 一定要把它包裝成 object 不可,所以弄了一個怪東西來代表一個簡單的概念(函數)。這種情況在 Java 同樣出現,你需要將該 function 包裝成一個 anonymous class 的 instance 才行。

有沒有感覺這是「為了 OO 而 OO」的作法。
其實,物件是人直覺的東西,功能也是人直覺的東西。為何人要這麼堅持呢?不懂!

幾年前曾出現過 Java 是否需要 closure 的爭論,Gosling (Java 的設計者) 認為 Java 並不需要closure,提出的理由是可以使用 anonymous inner class 來取代 closure。

Gosling 沒有說錯,anonymous inner class 確實可取代一部份 closure 的功能,但只是一部份,也就是 event handling 的那部份。

這種解釋與作法讓 Java 降低立即需要 closure 的壓力,直到新語言 Ruby、Groovy、Python 的挑戰出現。

在當時要求 Java 支援 closure 呼聲滿天高的時代,Microsoft 的 Anders Hejlsberg (Delphi、C#的設計者) 也曾出來消遣一下 Java 使用 anonymous internal class 作為 event handler 的這種作法。但那時由於 Micrsoft 的 C# 與 Java 戰火正烈,Microsoft 的所有作為都被是為敵意,所以 Anders Hejlsberg 的說法,被當成政治語言解讀。實在可惜,直到現在我們還繼續使用這種 anonymous inner class 作為 event handler 的跛腳解決方式。

筆者不覺得 Gosling 不明白 function 的好處與能力,但每個語言有他的特色,相信在 Gosling 的心目中,Java 的設計哲學就是簡單,使用 anonymous inner class 來作為解決方案,應該是他衡量簡單與複雜的取捨。

一個簡單的語言,與複雜功能強大語言比較,要做到相同的功能當然顯得繁瑣。但語言的好壞與否,不應該只在於功能其大,而應該與他的目標使用者(programmer)的操控能力有關。

對一個有操控能力的 programmer,功能強大的語言是他的選擇對象。但作為一個語言設計者,若希望他的語言可以吸引大部份的 programmer,簡單也可能是一個好的方式。
也許 Gosling 是這麼堅持的,堅持簡單就是美,雖然演化到現在的 Java 也已經一點都不簡單。

另一個讓 Gosling 沒有採用 closure 的因素,可能在於實現(implementation)。

各位若瞭解 JVM 與 Java classfile 的格式,你可能會驚訝 Java 之前為支援 generic 所付出的代價。

為了支援 generic,除了有一大堆 class library 需要改寫支援 generic 外,更重要的是 Java 需要翻修最底層的架構,JVM 要改、classfile 結構要改,這個工程可是大翻修,非常冒風險。由於翻動過大,雖然到了現在,我們還可發現 generic 翻修所留的痕跡,type erasure 就是一例。

在 Java 的 generic 中,有 type erasure 的問題,讀者可不要來跟我說這是特性(feature),compiler 看到與編譯出來的不同,就叫問題。
我們以下例來說明
List lst1 = new ArrayList<String>();
List lst2 = new ArrayList<Integer>();

上面的 lst1 與 lst2 經過compile後在classfile中所呈現出來的type,兩者都是ArrayList<Object>,無法清楚分辨出來。

「但從 Java source code,我們可以清楚分辨 lst1 與 lst2 不是同一個 type 啊? Java compiler 這麼差?」

這麼說都說對了,雖然從 source code 中,我們確實可以瞭解 lst1 與 lst2 型態的不同,而且 Java compiler 有能力可以分辨出這兩者的不同(否則你試著將上例中的 lst1 設定 lst2看看,看 compiler 是否允許),但產生出來的 bytecode(放在 classfile中),就是沒辦法分辨。

並不是 Java compiler 差,導致出現這個問題,問題原因出在 classfile 的格式。Java classfile 並無法容納 generic type 中 type 參數的實際資訊,因此 Java compiler 會先移除 generic type 中 type 參數的實際資訊,再產生 bytecode 儲存到 classfile中。

Java compiler 移除 generic type 中 type 參數的實際資訊,這個動作就叫做「type erasure」。

至於為何 classfile 無法容納 generic type 中 type 參數的實際資訊?很簡單,那就是會太複雜。
要容納這些資訊的話,classfile 的格式會變成太複雜,更動的幅度會過大,以至於很多工具無法好好有效的使用 classfile,原因很簡單吧。

這個簡單的因素,到現在仍繼續震撼 Java 的世界,使用 Java 泛型的 programmer 繼續受這個決定的影響。
這個因素甚至也影響到 Scala 的 programmer(你以後會知道 Scala generic 因為 type erasure 的原因, 需要花多大力氣來修正這個問題)。

你覺得 Java 開發團隊的人不願意好好處理這個問題嗎?應該不是。但筆者悲觀的認為他們不會再處理這個問題,因為影響太大,已經變得無法處理。

對於筆者而言,筆者倒希望 closure 之前沒被 Java 採納,真正的原因是,Java 團隊的人考量「簡單與實現的困難度」所得到的最合適結果。

現在最新的 Java 發展,closure 仍是大家關注的對象,雖然進度那麼令人不滿意。
Java 的 closure 叫 Lambda Expression,雖然已經是七折八扣的鳥籠 closure,但直到 Java 7 還是不支援,現在最新的消息是要到 Java 8 才支援。
由上面解釋泛型的經驗,我們知道要在既有的架構中,加上一個大的變動並不容易,所以對 Java 演進的期望值沒那麼高,但 Oracle 也讓我們等太久了。

在舊輪子開新車不容易,既然要 Java 做改進這麼的不容易,難怪就有些人提出,乾脆 Java 或 JVM 從今不再做進一步的演進,請 Oracle 推出新語言,來滿足新需求,也許更簡單俐落。

到此,讀者應該要有這樣的認識,就算 Java 或 JVM 會繼續做修正,幅度與演進速度也不容易滿足我們的願望,與其等待 Java 的演進,不如盡早找一個與你既有的 Java 程式碼可以有效共同運作的新語言。這個新語言,筆者認為 Scala 會是一個好的選擇。

關於 function 與 closure 這個需求,在 Scala 世界中的我們,可就幸福了。函數與 closure 在 Scala中都非常完整的被支援,雖然不像純函數的語言那麼令人驚豔,但夠我們享用了。

14. Scala 與新的語言功能 --平行處理

平行處理是一個非常重要的程式設計方法,你幾乎不會不遇到它,在未來越來越多複雜的程式應用中,平行處理一定跑不掉。

「平行處理,我會啊,Java裡面不就不包含有 thread,用它就沒錯了」

沒錯,Java 一開始出來時,在語言層級直接支援 thread,以那時的時空背景,確實是令人眼睛一亮的特色。

「Java有 thread、有 synchronized (lock),還有一大堆豐富的資料結構供我們使用,還不夠嗎?」

以上確實都是使用 Java 撰寫平行處理程式時很重要工具。
可是,你若曾經使用 Java 的 thread 來開發過程式,你會發現並不是那麼好寫。你會遇到不少狀況,不是程式不小心就給你停住(deadlock啦),要不就是資料有時對有時不對(發生 race condition 了),要讓資料穩穩的走,實在不容易。

以上的這些感覺非常正常,不單是你,這也是大家都困繞擾的問題,因此才會持續有平行處理的解決方案出現。

Java 直到現在仍無法擺脫平行處理的夢靨,不單單是 Java,平行處理幾乎是所有語言面對的共同難題。雖然到了 Java 7,Java 對平行處理的解決方案,仍然顯得不夠完整。

Concurrency 這麼難纏主要的問題出在於共享的資料上,各個 thread 在不同的時間去存取同一個資料,造成資料的不一致。
為要解決這個不一致的問題,lock、critical section 等各種解決方案老早就出現。

但有了這些解決方案,我們還是要很小心,執行以下的步驟
1. 在多個 thread 會共享到的 class中,一個個加上 synchronized 來保護我們的共享資料。
2. 避免加上太多的 synchronized,以免失去了concurrency的好處。

這在簡單的應用中不會有問題,可是你是否發現,當你的資料一多,或是資料結構間互相使用的關係變多時,問題就來了。synchronized 不好好檢查與運用,deadlock絕對有你的份。

你若上網查資料,你可能會找到許多在 Java 中開發 concurrency 的好方法,其中可能包括以下的 guideline
1.盡量使用 final 的資料
2.lock 的順序需要保持一致(避免 deadlock)
3.盡量使用 Local Thread 變數
4.盡量使用 stack 中的資料,也就是參數或 method 中的 local variable

有這麼多的建議,代表這個問題的困難程度。以上這些建議都是好的,都有機會解決你一些困擾,可是為甚麼還是困難重重?

Doug Lea在 Java 界是一個 concurrency 的大專家,java.util.concurrent 是他的傑作,你沒看錯,Java 附的 concurrent package是他做的。他的 Fork/Join framework 是 Java 7 解決 concurrency 的重要解決方案,你若繼續使用 Java 開發程式絕對不能錯過。

由 Doug Lea 仍持續加強它的 concurrency framework,你就知道 concurrency 多麼難以處理。

在各種語言中解決 concurrency,最被人稱許的,大概數 Erlang了。
Erlang 一開始就號稱使用在電信產業,那種需要特別穩定與超高數量級的資料處理中,Erlang 對於 concurrency 確實有他的一套。

Erlang 將 concurrency解決得這麼穩定,在於以下的手法
1.No shared state:不要有共享的資料。
2.Lightweight processes:使用 process 為主要的處理對象,這裡的 process 不是 OS 中的 process,請不要誤會。
3.Asynchronous message-passing:使用訊息傳遞作為不同 process 間的溝通機制
4.Mailboxes to buffer incoming messages:使用 mailbox 儲存 message
5.Mailbox processing with pattern matching:使用 pattern matching 來比對
上述這些手段就是所謂的 Actor Model,當然這種觀念不是 Erlang 所獨有。

除以上幾段外,Erlang 可以完整解決平行處理的問題,其手段還包含
1.儘量使用函數
2.儘量使用常數
3.沒有 lock
4.沒有 side-effect

這些觀念都非常的重要,都是寫好 concurrency 程式的重要概念,請努力記住。在你學習 Scala 的過程中,這些都是一再要提醒自己的事。

Concurrency 絕非天上掉下來的禮物就自然解決的,許多觀念與思考邏輯的改變,才是解決 concurrency 的不二法門。

網路上有許多文章號稱 Scala 的 actor model 完美解決 concurrency 問題,你若因為這些文章來學習 Scala,你會大失所望。 Actor model 不是解決 concurrency 的萬靈丹,你的觀念才是!

單純的以為 actor model 的 message passing 就可解決 concurrency 這個大問題,Doug Lea 還需要那麼辛苦幹嘛?!

再回頭看清楚 Erlang 處理 concurrency 的原則,actor model 是其一,更重要的在後面,函數、常數、沒有 lock、沒有 side-effect,請看清楚,多看幾遍,咀嚼這些的含意,為何 Erlang 還需強調這些東西。
Data sharing 是 concurrency 的問題來源,直接解決這個問題來源才是做好 concurrency 的根本。但我們的程式通常是攪和來、攪和去,運行在單一 thread 上當然可以,多個 thread,嗯嗯...。請注意,等候 method 的 return 也是一種 data sharing。

這裡順帶一提,因為 lock 很容易造成 deadlock,所以使用 synchronized 來 lock 資料的機制,在 Scala 語言已經被移除,Scala 以沒有 synchronized 這種語法,當然你還是可以使用其他的 method 加上。
怪吧!這麼好用的 lock 機制,竟然被移除,違反你以前的學習觀念吧?!

為何要移除 synchronized 呢?當然是有其原因。Scala知道共享式資料才是造成 concurrency 問題的最大元兇,所以降低資料共享才是真正解決 concurrency 的最正確作法。
在 Scala 中會一再要求你,盡量符合以下的規範
1.盡量使用不可變的資料:變數使用 immutable 與 val
2.盡量使用 function:最好是跟純函數式的語言一樣,使用沒有 side-effect 的函數
3.盡量不要資料共享
4.使用 actor model,並使用 asynchronous message-passing:在 actor 中使用非同步的訊息傳遞
5.不要 lock:既然上面都已做到,已經不需要使用 lock 來保護資料了(此時使用 lock 反而容易造成程式 deadlock 的可能性)

第4點的非同步傳遞訊息,就是不等待對方回應的訊息傳遞,訊息傳遞完畢後就回來做自己的事,不需等待對方回應。

「為何不等待對方回應這麼重要呢?」

因為呼叫方 (caller) 若等待被呼叫方 (callee) 回應,callee 為要達成 caller 所要求的事項,可能需要進行一大堆的工作,而這些工作可能包含 callee 回頭呼叫 caller 的工作,若 callee 回頭呼叫的方式也是需要等待Caller完成才會繼續執行,這時問題發生:
1. caller 呼叫 callee 並等待
2. callee 呼叫 aller 並等待
以上兩種情況一發生,那就叫 deadlock,無解了,程式自然停下來,乾瞪眼!

所以非同步的訊息傳送才會這麼重要。但實際狀況卻是「同步的訊息傳送」有時避免不了,這時,你需要謹慎使用「同步的訊息傳送」。

平行處理是一個大問題,可是卻也是一個非解不可的問題,Scala 準備好各種好用的工具與寫程式的準繩,就看你會不會好好利用。
若你順著 Scala 幫你準備的東西,其實平行處理並不像想像那麼難,而且會這些工具還會讓你程式開發的速度與品質大幅增加。

13. Scala 與新的語言功能 --泛型 (Generic)

什麼是 Generic,讓我們舉最簡單的例子 Array 的排序。

關於排序的觀念很簡單,就是將 Array 的 element 依照由小到大或由大到小重新排列一次,這就是排序(sort),概念很清楚,但實做起來可是非常的麻煩。

在沒有泛型概念的時代,programmer 需要為各種型態寫出各個相對應的程式碼。

我們需要為整數些一個 sort_int 的 function,需要為浮點數寫一個 sort_float 的 function,需要為字串寫一個 sort_string 的 function,很累吧!
雖然這些 function 幾乎大同小異,但就是不能共同使用。寫這些 function 除了要 copy_paste 之外,最大的問題是,若你的 function 概念有點不對,有 bug 需要修改,那你可要展開你精明的頭腦,注意改對所有的 function,一個都不能漏掉,一個都不能改錯。

還有一個問題,如果你加了一個新的型態,比如複數 (complex number),那要記得把舊的 sort function 重新複製一份,否則你的 sort 是無法排序複數的。

很煩吧?多年以前 C++ 為了要改善這個問題因此加入了 template 的概念,也產生 STL (Standard Template Library) 供 programmer 使用。
Template 可以讓 programmer 寫出共同的部份程式碼,不需為了不同的資料型態重複寫類似的程式碼。

Java 當然後來也加入的這像特性,這種特性就叫 generic(泛型)。 Java 並大量使用泛型在它的共通資料結構中(如 HashMap、List、Se t等),你在使用 Java 當中應該使用過這些資料結構。

你的程式除了使用泛型的程式碼外,你也應該撰寫過一些泛型的程式碼,以工期他的程式呼叫。沒寫過?那你要多加油喔。

Scala 承繼 Java 的泛型觀念,並且把它擴充的更完整。Martin 可是 Java 泛型的開山始祖,在 Java 尚未有泛型之前,就設計過 Pizza Language 與 Generic Java 這兩個 Java 的泛型機制。Scala 引進型態參數 (Type Parameter) 的概念,讓泛型更嚴謹,更不容易寫出錯誤的程式碼,保障程式碼品質。

多了 Type Parameter 的概念,也就多了一道頭腦需要轉換,因此顯得更複雜,但這是需要付出的代價。

12. Scala 與新的語言功能

這十幾年以來不斷有新語言問世,代表這個世界對程式語言的期望。

一個程式語言的出現一定是要解決某一類問題,而這一類特殊問題使用既有的程式語言來解決,就是顯得繁瑣或難以處理。

Java 是那麼的流行,當有新語言興起時,通常會引起將該新語言的特色納入 Java 語言中的討論。

我們先來看 Java 流行以後,大家希望對 Java 的加入的功能
1. Generic(泛型)
2. 並時處理
3. Functional 與 closure
4. Continuation
5. Meta Programming

這些項目也是一些新語言嶲具備的特色,讓我們依序討論,並說明 Scala 如何解決這些問題。

11. Scala 與 JavaScript

上篇提到,其實個人最希望它支援的標的是 JavaScript,好像有點混淆是不是?

請參考一下這篇說明(http://ejohn.org/blog/running-java-in-javascript/),有人提到把 JVM 搬到 JavaScript 上,就是說 Java bytecode 可以直接在 browser 中的 JavaScript 環境執行,這表示你的 Java 程式可以在 browser 中執行,那是多麼令人高興的事。

撰寫這篇文章的老兄叫 John Resig,他是 jQuery 的原始作者,我想這樣說大家就知道他的份量(阿?不知 jQuery,那你一定沒有使用 Ajax 寫過程式)。
直接使用 JavaScript 製作出來的 JVM 來執行Java bytecode 效能一定不會好,因為每個 JVM instruction 變成一個 JavaScript method 執行,效果不差才怪。

為甚麼一定要將 Java 程式碼,或把 Scala 搬到 JavaScript上面執行呢?原因很簡單,因為可以跨平台!

自從 Html 5 問世以來,JavaScript 逐漸在各個重量型 browser 統一,其中有一個很重要的東西叫canvas,canvas就是可以讓 programmer 在 browser 中建立一塊畫布,然後在這塊畫布中亂畫一通,也就是可以 canvas 可以完整執行你的 UI 程式。各個 browser 包含IE9、Firefox、Chrome、Safari、Opera都宣稱(或已支援)canvas。

微軟為何在這次 Html 5 的規格中,不像以前一樣,仗著市佔率加以抵制甚至另開新規格來搗蛋,而是很快在新版的IE9就支援?
筆者個人認為其實不是不搗蛋,而是不敢搗蛋。

「不敢搗蛋?會嗎?雄霸 PC 的微軟會低頭?」

不是 PC 的問題,是手持裝置的問題,現在市場中大家關注的是 iPhone、iPad、HTC,沒見到微軟的 Windows Phone。
滿坑滿谷手持裝置都是 Apple iOS 與 Google 的 Android,不巧的是這些手持裝置的 browser 就是 Safari 與 Chrome,這兩個 browser 早勾結在一起想要扳倒微軟的 IE。

機會來了,這兩個公司仗著手持用戶的勢力,硬是推出新的 Html 5 與 Canvas 標準,你說微軟買不買帳?

想一下以下的情境:你有些應用程式在手持裝置上執行好好的,可是回到 PC 的 IE 就狀況百出,你這時會如何?

是不再用手持裝置嗎?還是在 PC 灌上可以執行該應用程式的 browser?在 PC 加灌 Safari 或是 Chrome 可是件輕而易舉的事!
為保IE的地位,你覺得微軟會搗蛋嗎?
這是不是風水輪流轉,今朝你打我頭,明晚我踢你腳!

「能夠使用 canvas 有那麼重要嗎?」
為甚麼使用 JavaScript 來寫程式很重要呢?原因是現在各個 OS 的 Native Application 作法並不一致,很令人頭痛。
若你想寫一個跨平台的應用程式,在各平台都 porting 一次,是必須付出的代價,很煩。

這些平台上有一個共同的東西,叫 browser,如果我們可以在這共同的東西開發程式,不就畢全功於一役?!
只要寫一次程式就好,而且程式還不用安裝,直接由網路直接執行,真是美好。

想想看,如果有一個很像微軟 Office 操作方式的軟體,直接使用 bowser 就可執行,對用戶是多麼方便?!

以前的 browser 彼此支援程度都不同(可能是競爭問題,故意支援程度不同),所以幾年前你可以看到有一些書討論如何寫好跨 browser 的 JavaScript,這是多悲慘的事, programmer 成為大廠競爭下的犧牲品。還好,這種狀況現在比較少見。

隨著 Html 5、Canvas 的出現,以及各大 browser 的充分支援,想要撰寫「跨  browser、且讓使用者操控簡單」的應用程式,變得很有機會,所以筆者認為,JavaScript 與 Html 5 未來在程式開發將扮演重要的角色。

Html 5 與 Canvas 推出之時,Google曾經 demo 在 browser 開發 Doom 遊戲。很炫吧!

「以前難道沒有這種跨 browser 的東西嗎?」

以前的 browser 彼此支援程度不同,所以這個跨 browser 的重責大任就變成第三方單位的事。

第一個挑戰跨 browser 寫應用程式的就是 Java Applet。
藉著當時 Netscape Browser 的威力,讓 Java Applet 紅極一時,並且迫使 IE 不得不支援 Applet(微軟可是心不甘情不願,奈何那時 browser 正紅,而霸主叫 Netscape)。
只是 browser 給了機會,Java 卻不爭氣,硬生生將市場讓了出來,現今 Applet 雖然仍然存在,但已式微。筆者那時寫的 Applet 程式,全如流水,嘆!

第二個挑戰者就是 Flash。
很有名,那個動來動去的動畫是它傑作(很久以前,你曾看過幹譙龍?),那個看影片的東西也是它(YouTube 你總知道了吧!),還有還有,它可以拿來撰寫 browser上的 GUI 的程式(你看過在 browser 執行很像 Outlook 的 Email 程式嗎?)。

仗著在各瀏覽器超高的安裝率,Flash 幾乎成為 browser 中寫 GUI 程式的標準,然而功虧一簣,它的後台老闆終究叫 Adobe,不叫微軟、Apple 或 Google,力有未逮。

其實 Adobe 還有一項氣人的,在 Flash 中它弄了個很像 JavaScript 可是就不是 JavaScript 的 ActionScript,搞得大家暈頭轉向。Programmer 工作已繁忙,但光在 browser 端,除了 JavaScript 還要重學一套 Adobe 的怪東西,難怪很多人不爽,雖然 Adobe 宣稱 ActionScript 與 JavaScript 多像。真是奇怪,既然像,就用相同的就好啊。ActionScript 雖然會的人不少但不會的人更多,所以自然不成氣候。

對於 Flash ,基於保護 programmer 的立場(你信嗎?),Apple首先跳出來發難,仗著手持市場霸主的地位,趁著 Html 5與JavaScript Canvas 的推出,iPad 支援 Html 5 與 Canvas,硬生生宣佈不支援 Flash,理由當然冠冕堂皇,但真實原因應該就是不爽 Adobe,請離開我的地盤。

Flash 除了那個動來動去的東西以外,影片被 WebM 取代,GUI 的程式部份被 Canvas 取代,不知 Flash 好日子還有多久。

Browser 是各個平台的標準配備,許多大廠(比如Google、Apple)也鼓勵大家使用 browser 開發網路應用程式,因此 Html 5 與 JavaScript Canvas 很有機會變成重要的撰寫程式工具。

既然 JavaScript 這摩重要,我們應該好好使用 JavaScript。壞就壞在 JavaScript 實在太自由,要寫出個嚴謹的程式並不容易,連它的 Object Oriented 觀念都與主流不一樣,使用 prototype-based,與大多數的 class-based 不同。就是一句話,難進得了大廳堂。

因為 JavaScript 這麼的重要,但又這麼的不勘重用,所以有人提出 JavaScript 為 Platform 的想法,也就是將 JavaScript 變成被編譯出來的目標碼。

這可不是筆者亂說的,Oracle 的 Java 也有使用 JavaScript 製作 JVM 或是將 Java 程式碼 compile 成 JavaScript 的打算。Oracle VP: “We have a strategy to run Java inside a Javascript environment”(參考http://www.taranfx.com/java-javascript-runtime-environmenthttp://cemerick.com/2010/12/12/oracle-vp-we-have-a-strategy-to-run-java-inside-a-javascript-environment/)大家可以看看。

對於 Oracle 這項偉大的計畫,筆者舉雙手贊成,如果成型,我們這些 Java 的愛用者,就可以大大方方將 Java 程式轉移到 browser 執行,想到就高興。但可不要弄個「使用 JavaScript 來製作JVM」這個老梗(這種事也不需 Oracle 這種大廠來做,早有人完成了),更希望這不只是喊喊口號。

既然有人提出將 JavaScript 當成平台的觀念,筆者衷心希望 Scala 團隊有將 Scala 的原始碼編譯成 JavaScript 目的碼的計劃。若是達成願望,以後寫 browser 的程式,可就輕鬆多了。咦?這不也是 Scala 的目標之一嗎? Martin 舉例中,不是希望大家以後不需再撰寫 JavaScript 嗎?Martin,可見得你的 Scala 還缺了 Web Client 這段還沒解決,這是 Scala 欠大家的。

10. Scala 的企圖心,也是它的特性 --JVM 還是 .Net,還是...

上篇提到 Scala 與 Java 的關係,實在很密切,可以把 Scala 看成是 Java 的另一個面貌。

Scala 既然與 Java 關係這麼密切,那 Windows 這陣營的人,看起來與 Scala 無緣了。

那你錯了,支援 .Net 平台是 Scala 重要的功能,Scala 除了可將 Scala 原始碼編譯成 JVM bytecode,也可以編譯成 .Net 的 CLR,因此可以在 Windows 平台上順利運行。

至於 Scala 與 C# 合作關係是否可以像 Scala 與 Java 的合作關係,答案當然是否定,或許這是未來 Scala 開發團隊所應該持續努力的。
但可以確定的是,你至少可以把 Scala 當成是開發 .Net 應用程式的另一個程式語言,就像其他 .Net 的程式語言一樣。

Scala 除了可以 compile 出在 JVM 與 .Net 平台的標的碼外,還可支援其他的嗎?Native machine code 可以嗎?LLVM 可以嗎?還有其他的嗎?

現階段 Scala 只支援 JVM 與 .Net,但 LLVM 似乎已在 Scala 的支援預定項目當中,只是不知何時現世。

對於 natvie machine code 個人也很好奇為何沒有,也許書沒有讀透,找到之後再來更新。

其實個人最希望它支援的標的是 JavaScript,好像有點混淆是不是?我們下篇再談。

9. Scala 的企圖心,也是它的特性 --Scala 與 Java之間,可以混用

Martin 是 Java 的愛用者,曾經也根據 Java 的缺失,提出一些調整方案(你可以去找找這個老兄的事蹟,這裡不詳加說明)。

既然是 Java 的愛用者, Martin 不會輕易放棄 Java 這個世界的龐大資產。Java 的龐大資產,指的有兩方面,一方面是指所有語言中最多 programmer 的語言,另一方面是指有豐富的 library。

為繼承這兩大的資產,Scala 使用以下的方式來爭取:
1. 為吸引 Java programmer,所以 Scala 的語法精義與 Java 非常相同。
2. 為讓 Scala 的 programmer 使用 Java 豐富的 library, Scala 與 Java 可以混用。

「什麼叫混用?」

Scala compile 出來的東西,是正常的 Java bytecode,可以放到一般 JVM 執行。而且,既有的 Java 程式碼,Scala 可以輕易使用,Scala 的程式碼,Java 程式也可輕易使用。

混用就是一個專案中可以有一些是Java code,一些是Scala Code,兩者混合在一起。Scala 與 Java 者互相把對方當成自己人。

Martin 把 Scala 的 OO 體系設計的非常類似(其實應該是,Scala 的 OO 體系是 Java OO 體系的 superset),以 JVM 角度來看,Scala 編譯出來的 bytecode 是完全正常的 bytecode,與使用 Java compiler 編譯出來的 bytecode 完全相同(當然會有一些額外的method),因此 JVM 無法分辨原始碼是 Java 產生還是 Scala產生的。

以 Scala 程式的角度,Java 所寫出來的 class,就是一般 Scala 的 class,Scala可以直接使用。
其實,在Scala 語言中已經直接使用 Java class。String 這個 class,Scala 並沒有特別加以包裝,而是直接使用 Java 的 String 這個 class,可見得 Scala 把 Java 當成同樣的人看待。

以 Java 程式的角度,Scala 所寫出來的 class,也是一般的 Java 中的 class,所以 Java 可以直接使用。

Java 與 Scala 的互相使用,並不是單純的呼叫,而是可以互相繼承。Java class可以繼承 Scala class,Scala class 可以繼承 Java 的 class,這叫混用。

你可以想像成「Scala 與 Java 世界,兩個是同一個世界,只是 Scala 是 Java 的另一種表達方式」,或是,「Scala 只是另一種語法的 Java」,對你在學習 Scala 會更快入手。

8. Scala 的企圖心,也是它的特性--效能

一個有彈性的語言,通常效能不會太強調。這種特性,你可以在很多 script 類型的語言中發現。

這其中的原因,有些是因為要維持彈性,所以很多東西只能 runtime 才來檢查,導致效能較差,有些是因為要動態編譯,所以自然就慢。

Scala 呢?會不會功能變大了,所以效能也變慢了。若是變得太慢,可會有許多人不想移轉過來的,現在還有這麼多 C 的使用者,效能考量是主要原因之一。

這個考量,Martin 早就知道了,在 Scala 的 compiler 中,會盡量編譯出最好的 bytecode 出來。

根據實驗數字,解決同樣一個問題,使用 Java 與使用 Scala 撰寫出來的程式,效能差不多,兩者在伯仲之間。不會像 Groovy 那樣,與 原有 Java 的解決方式,有很大的效能落差。

所以,你若是Java的愛用者,不需為考量 Scala 的效能 而不敢進入 Scala 的世界。

7. Scala 的企圖心,也是它的特性 --純 Object Oriented 與不純 Object Oriented

有很多推動 object-oriented 編程的人,如果語言參雜了不是 object 的東西,就會感到渾身不對勁,有人稱他們為「OO 的基本教義派」。

這些人是有他的道理的,因為若不完全 OO,程式邏輯被強迫分成兩半, 一些程式碼使用 OO的方式思考,一些程式碼需要使用資料的方式來思考。而且有些東西不是 object ,需要付出應付非 object 的心力,造成程式複雜性較高。
對於 OO 基本教義派,以上的情況,會讓他們感到不夠乾淨、不夠完整,好像整個思考架構有了缺陷一般。

這種純淨的堅持,導致一些純淨 OO 語言產生,如 Smalltalk 就是這種純淨的OO語言,所有看到的東西,都是 object,沒有例外。

所有東西都是 object,有邏輯一致性的優點,但卻產生效能 (Performance) 的缺點。

原本電腦對於一般的資料運算,本來就有它的格式,所有的數值運算鎮 正執行時,都是使用電腦本身既有的資料格式。

電腦原來支援的資料格式有整數、浮點數、布林等。對於這些原本的資料格式,若把它包裝成object,在執行這些資料的運算時,勢必增加資料與物件轉換額外,最後的結果就是拖慢整體運算的時間。

為要加快這種常見型態的運算速度,有一些 OO 語言選擇直接支援這些常見的資料型態,因此除支援 object外,還支援數值與布林等資料型態。這種 OO 語言就被稱為混合型的 OO 語言,Java 就是這種混合型的 OO 語言。
在Java中,這種原始電腦格式的型態稱為原生型態(Primitive Type)。

對於混合型的 OO 語言,有許多 OO 基本教義派沒辦法接受,認為這種語言簡直就是破壞 OO 的完美,也因此產生 Pure OO 與 Non-Pure OO 多年的爭論。

你有沒有感覺到一件事,就是純 OO 派的這種論述,與之前純函數式語言的論述有點相同。

函數式語言的基本教義派是什麼東西都要是函數與值,所有的東西都要沒有副作用(確保每次函數執行都會得到同一個值),若是參雜了一些命令式的東西,有人也會渾身不舒服,覺得思考架構產生缺陷。

為甚相同的感覺重複出現呢?難道這就是純潔的追求嗎?還是只是文人相輕呢?

純 Object Oriented 與不純 Object Oriented 是 OO 語言延續多年的一個大論戰,各有支持者。
那 Scala 的設計者怎麼說呢?我想,你應該知道標準答案吧!為了要支援各種應用(還是,更正確的說,要吸引各種應用的使用者),所以 Scala 兩種都支援!

「別傻了,這種問題哪裡可以兩種都支援?!」

嗯,確實是兩種都支援,只是取巧了點,也讓 compiler 辛苦了點。
怎麼說呢,在 Scala 語言中, 支援的是純 OO,所以每個東西都是 object(純 OO 的支持者,到此,可以放心了),但對於有原生型態的資料類別,會採取特別的方式。
在 compile 階段,若看到原生型態的資料類別,會直接生成 primitive 資料,而不是擴展成object,這就是它支援 pure-OO 但有混合型 OO 效能的方式。

你有沒有發覺,Scala的手法很一致,在爭論的兩造,都是以支援嚴格的為主,但容納另一方的優點。
Static typed 與 dynamic typed 是這樣處理,pure-OO 與 Non pure-OO 也是這樣處理。這樣,它可以爭取到兩方的支持者。

6. Scala 的企圖心,也是它的特性 --script 與直譯環境

上篇說到 Scala 本身主要使用 compiler 的方式,但也支援 interpreter,那可以用 Scala 來寫 server 端中的 script?

通常 script 為 server 中常見的程式,主要是為了執行一些反覆執行的工作,把這些反覆執行的工作寫在一個 script 中,然後在適當的機會執行起來。

在 Unix 環境中有 bash,在 Windows 環境中有 bat 等,可以任你撰寫 script 程式。除這些基本語言外,也有一些特別為 script 衍生出來的語言,如 perl 或是 windows 環境中 Power Shell 等。

既然 Scala 想要支援各式的應用,script 它也想到了,我們可以使用 Scala 來撰寫 script 類的程式。

使用 Scala 寫 script,程序很簡單,只要將 Scala 原始碼編寫好,存放在某個檔案中(通常為 .scala),我們就可以使用 Scala interpreter 直接執行該檔案的程式碼,這種方式很像是一般的 script 執行方式,只不過編寫程式的方式改為Scala。你也可以將該Scala程式碼使用cron job來執行。

使用 Scala 撰寫 script 有個好處,就是以後不用再受 bash 有限功能的痛苦了。

另外,Scala 也附有一個 interpreter 直譯環境,你可以在該直譯環境編寫、測試你的Scala程式碼,很像是 Basic 或是其他直譯的環境,很有有意思!
也就是,你若是寫慣 Basic 的人,Scala 也不放手,你可以學會 Scala 語法後,像 Basic 直譯的方式來編寫你的 Scala 程式。

5. Scala 的企圖心,也是它的特性 --compiler 還是 interpreter

Compiler 還是 interpreter?在選擇一個語言時,這也是一個選擇因素。

Compiler 需要重新 compile 所有程式碼,轉成 machine code(在 Java 中轉成 bytecode),再執行machine code(或 bytecode),所以通常執行速度會較 interpreter 的方式快。

Interpreter 直接吃原始碼,執行時需要先轉換原始碼再執行。通常用 interpreter 方式的執行速度,會較使用 compiler 方案的速度慢。
其實使用 interpreter 方式,除了執行期間需轉換原始碼外,動態型態也是執行慢的重要因素之一。但使用 interpreter 方式的好處是開發時快,修改完程式,馬上可以執行,不用等待 compile 的時間。

在 C、C++ 的專案中,通常若是專案夠大,compile 的時間確是一段需要考量的因素。但 compile 時間在 Java 環境中比較小,Java 由於它的特性,linkage 的部份延遲到 runtime 時執行,因此 compile 的時間通常不會到讓人難以忍受的地步,所以使用使用 compiler 的方式顯然較佔優勢。

Scala 延續 Java 的使用 compiler 的方式,但為了支援 script 的寫作方式(別忘了,Scala 想要取代各式的應用),所以也支援 interpreter 的作法。
若你討厭每次原始碼寫完要 compile,你可以寫一段Scala程式碼,直接讓 Scala interpreter 來執行,雖然這種情況,在 Scala 世界非常少見。

4. Scala 的企圖心,也是它的特性 --動態型態還是強制型態

設計一個語言,尤其標榜快速開發的語言,常常遇到一個問題,到底是靜態型態還是動態型態?

所謂靜態型態(有時會稱強型態,strongly typed),表示變數需要預先宣告型態,當指定某個值給該變數時,compiler 需要檢查型態是否正確,正確才會放行。

這好像太囉唆了,沒錯,囉唆就是它弱點,但這可以預防你出錯,所以安全是他的優點。
當你程式寫錯了,compiler可是會預先幫你抓出問題,至少,不會讓迷迷糊糊的我們將變數名稱打錯了還不知道,所以標榜企業端開發的程式語言大多支持靜態型態。C、C++、Java 系列的語言都是強型態的語言。

至於動態型態(或稱弱型態)表示變數的型態可以變換,現在是字串,下一個命令可能變成整數,甚至連變數都可以不預先宣告就可以使用。
好處就是開發自由度很高,壞處呢,就是亂,品質需靠嚴謹的測試,compiler 或 interpreter 連點小忙都無法可幫。
標榜快速開發的語言通常支援動態型態,鼎鼎有名的 web browser 的主導語言 JavaScript 就是動態型態。

動態型態有開發的方便性,靜態型態有 compiler 代為檢查的安全性,何者為好,也是多年的爭議了。
你若是一個語言的設計者,該如何選擇呢?看看 scala 的選擇方式,Scala 要動態語言的方便性,也要靜態語言的安全性。

「是真的嗎?不會是吹牛吧?」

是真的,Scala 知道要有快速開發的好處,所以深入研究過動態型態的使用方式,動態型態的優點可以歸納為
1.型態自動設定:型態不需宣告
2.型態自動轉換:不同的型態指定時可以自動轉換
3.變數不用宣告就可使用

除了變數不用宣告的怪動作外(這個好處其實隱含重大的缺點,就是你不可以打錯變數名稱,否則,除了 trace 程式,你無法知道你打錯了),前兩項 Scala 用了更加優秀的方法來取代。

Scala 的解決方式是維持靜態型態,並透過型態推論 (type inference) 的機制,讓你宣告變數時不用一定要宣告型態。Compiler 會由指定給該變數的值來推論出該變數應該具有的型態。

至於型態自動轉換,Scala 可以透過隱含式轉換 (implicit conversion) 讓變數自動轉換為另一種型態,因此可以維持動態型態編寫的簡易與自由度。

「什麼是型態推論 (type inference),什麼是隱含式轉換 (implicit conversion)?看不懂啦!」

沒關係,這裡當然看不懂,有興趣,請自行參考「釋迦樂、思家了」系列文章(http://sayscala.spotblog.com/)。

這裡只是要告訴你,Scala 的企圖心確實是大的,而且已經做到了。
它可以維持靜態型態的優點,並且納入動態型態的好處,所以如果你是動態型態的愛用者,不用擔心來到 Scala 的國度就變得礙手礙腳,Scala可以讓你維持之前的寫程式自由度,並且增加靜態型態的安全性。

 Scala 解決兩大爭端,命令式 v.s. 函數式、動態型態 v.s. 靜態型態,這兩種爭論都是延續一、二十年的爭論。

它讓各種優點都容納到它的語言裡面,你是否對它感到好奇,是否對它有了興趣?

3. Scala 的企圖心,也是它的特性 --命令式 (Imperative) 與函數式 (Functional)

上篇提到 Scala 有很大的野心,或說企圖心比較文雅。

Scala的企圖心是什麼?就是一次想要解決很多很多的事,最好把所有關於程式設計相關的事,全部使用同一種程式語言來解決。換句話講,就是想要讓設計師在各種應用都使用 Scala 來製作程式。

這個野心怎麼感覺起來...有點怪怪的?咦,怎麼好像有人說出來了,喔,這個企圖心簡單講就是一統天下啦,那是你說的喔。

Martin (Scala 的設計者) 可是使用了一個很優雅的名稱 Scalable,所以稱它為 Scala,至於 Scala 是不是只是掩飾,你判斷吧。

這段話是真的嗎?Scala 有這麼大的企圖心嗎?

有!我沒騙你。Martin 在他的 Scala 簡報中就說明到,Scala 希望簡化程式者負擔,讓各個領域的開發者都使用同一程式語言來開發,這不就是要用 Scala 取代各種不同領域的程式語言嗎?所以說,他的企圖心,或說是野心,是大的,大師終究是大師,要做轟轟烈烈的事。

Martin 在他 Scala 的演講提到程式者真的很可憐,不同的應用需要使用不同的程式語言,光是在他簡報內容出現的就有( Martin 簡報:http://www.youtube.com/watch?v=zqFryHC018k)
1. Web Client 端使用 Javascript
2. Server script 方面使用 Perl / Python / Ruby / Groovy
3. Busuness Logic 方面使用 Java
4. UI 使用JavaFX(筆者:真的嗎?)
5. Datatbase方 面使用 SQL

程式設計者很可憐,不是嗎?Martin 大發佛心來的,他要設計一個程式語言,讓大家解脫,只要學習一個程式語言,就可以在各個應用使用相同的程式語言。當然啦,這個程式語言就是一個有彈性的程式語言 (Scalable Language),因此命名 Scala!

Scala 的複雜性來自於想要把 programming language 的所有問題一次解決(就算不是所有問題,至少也是大部分的問題),以便一次解決各個應用的設計問題。

既然企圖心這麼大,那就要有解決所有問題的本事啦,第一個要做的事就是要能兼納各種程式語言的優點,否則我幹麼轉換現在的程式語言到 Scala。

「好,既然這麼行,那就考考你了。」

「OK,任你考。」

「先來調解一下爭論數十年的小問題吧,到底命令式 (Imperative) 語言好,還是函數式 (Functional) 語言棒?」

「哈,我是大海納百川,兩者通吃!」

「通吃?說真的假的?這兩者語言系列的邏輯,甚至語法都不一樣耶?!」

沒錯,這兩者實在不一樣,但 Scala 就要整合,而且要讓程式設計者用起來一致。
除讓這兩種語言特性的個別優點都能充分發揮,更要能好好的共處,程式設計者用起來就像是使用同一種程式語言一樣。

「會不會太神了?」

哈,個人我也是覺得有點神。當然啦,你要使用它就要有兩種頭腦,imperative 與 functional 兼具。難怪大家剛接觸時都覺得複雜,覺得頭痛。尤其是 imperative 的程式設計者接觸到 functional language 的感覺,「好玄」是大家共有的心內話,所以別怕,別人跟你也是一樣。

「阿,什麼素 iImperative,什麼素 functional?」

不要擔心,你若沒聽過 functional language,那你用的程式語言就是 imperative 的。舉凡常見的 C, Java, C++, Object,甚至 assembly 都是。
筆者也是 imperative 用慣的,我們的頭腦都是一串串 statement 累積下來,這種寫法就是命令式。

「那什麼是 functional 的呢?」

當有一種程式, 它使用一堆你看不懂的程式碼寫出來,你不仔細看,就很難看得懂(甚至仔細看也看不懂)的,那就是 functional,函數式語言了。

函數式語言顧名思義,就是寫程式很像在寫數學函數一樣,所以腦海裡的思考是哪一個函數要使用哪個函數,算出什麼值,所以都在描述函數的定義,不是在描述一連串的 statement。

玄吧?!

「怎麼 functional language 好像不做正經事,只寫函數就可以把事情做完嗎?」

好像是耶,而且確實也是耶。你會這樣想就代表你的頭腦被命令式 (imperative) 語法深深影響,未來學習 Scala 函數式部份時,會有許多頭腦轉換的痛苦掙扎(當然你也可以只用Scala的命令式的部份,就不會掙扎了)。

「有哪些是 functional language?」

很多,Smalltalk、Haskell、Lisp 都是,函數式語言會寫的人都說很爽,寫起程式來是飛快又容易對。
看到這種情況,對熟悉命令式的程式設計者簡直就是玄,那些人說的是真的還是假的?函數式語言有這麼神奇嗎?
確實是真的!

你若想嘗試看看 functional language 的威力,想好好學一下函數式的程式編寫方式,除了 Scala外,筆者認為 Haskell 會是一個不錯的選擇。

其實,如果你是命令式程式語言的 programmer,學習 Scala 的次序或許應該顛倒一下,先去學一下純函數式的程式語言,回頭再學習 Scala 會比較好,否則你很容易將 Scala 用得缺少了一半的能力,只剩命令式的那一半。

「那命令式與函數式兩者有什麼區別?」

函數式語言最大的特色是操作對象是函數與值,所以會將函數轉來轉去,值也轉來轉去,最常用的資料結構就是 collection,List 是很常見的一個代表。函數式語言強調程式中每個東西都是函數與值。

每次函數的執行只要參數相同,就會得到相同的值,這叫做「可重複性」。函數不應該有副作用 (side-effect),也不受執行時間與次序的影響。
Function 所使用以及所產生出來的值也都是不可變的(都是常數),不會有變數這檔子事。

這種特性在程式寫作上有它的好處,不會有共用與競賽 (Race-Condition) 的問題。在平行處理上,這種特性非常佔上風,你幾乎不用擔心多個執行緒所造成的問題。

命令式語言的本質剛好與函數式顛倒,開宗明義就是有一大堆資料結構(不管是否是使用 object 包裝起來),然後透過各個 statement 來修改,讀取這些資料,然後繼續後續的工作。
簡單講,命令式語言關心的是 statement 的次序與資料的修改與讀取。

命令式語言以資料修改為主,函數式以函數為主,二者關心的重點不同,思考邏輯也不同,因此才會顯得彼此格格不入,爭論數十年。

在命令式語言中有點點與函數式沾上邊,可能只剩下遞迴 (recursive)。
筆者這樣說,一定有很多函數式語言的基本教義派不認同。沒關係,這麼說只是想讓命令式語言的使用者有點函數式的感覺,請不要太介意。
阿,你不用遞迴,那你該回去修一下基本功了,完全用loop也不是辦法。

到這,你應該發現,命令式與函數式這兩者的差別就是天跟地,程式設計者頭腦轉的方式都不一樣。
Scala 第一個要解決的就是這個問題,硬是把它們整合在一起,而且在使用上要讓二者一致,不容易吧!其實,Scala 也不是第一個嘗試整合命令式與函數式的語言,也不用太大驚小怪。

除了命令式與函數式外,Scala 還要兼顧流行了一、二十年 Object Oriented 設計方式,更要接收 Java 的龐大資產(讓 Java 設計師很容易接受),所以你說,它怎麼會不複雜,功能又怎會不強?

說到這裡,要說一個小秘密,Martin 其實是有點小偏心。為了接收廣大 Java 的資產,Scala 的語法大量參考 Java 的語法精義。所以說他的設計方式是以命令式的 OO 為主,再行接納函數式的好處,妙吧。

個人覺得純函數式的程式設計者看到 Scala 的設計方式,應該也會大大吃不消,覺得離經叛道吧(通常,純函數式的程式設計者也是不習慣命令式的用法的)。

2. Scala 由複雜勝出?

C++ 複雜,所以被 Java 取代,作為一個新的語言,Scala 強調語言的豐富性,但卻也導致複雜。難道 Scala 反 Java 之道,由複雜取勝?

Scala 的複雜,有人形容它就像 C++ 一樣,這麼的難以親近,那它有機會成功嗎?

個人覺得 Scala 的複雜是來自於它的企圖心。
與 C++ 不同,C++ 在小細節上設計得很細膩,因此複雜。
Scala 的複雜,在於它想要處理的範圍過大,問題過多,所以複雜。兩者不太一樣。

學習 Scala 要有一個體認,這個語言包含的項目過多,因此要有吃不消、頭腦撐爆的心理準備。但你一旦熟悉它,以它的能力,卻可讓你省下許多繁瑣的工作,加速你開發程式的時程。

你有心理準備了嗎?想要學習嗎?不急!你若是專案纏身,已經趕得昏天暗地,看到這以為 Scala 是盞明燈,想要使用它來趕你現在的專案,我勸你靜一靜。

「為甚呢?不是說它可以加速開發嗎?當然要用它來趕進度。」

是沒錯,它可以讓開發加速,可是沒看到前面所說他的複雜度嗎?要好好運用它的能力,可非朝夕,若存著用它來解脫你現在的燃眉之急,恐怕從燃眉搞成燒頭。

程式已經寫不完,還要處理語言的不熟悉問題,只會將問題變得更複雜。建議你,先有一段學習的時間,之後再把它用到實際專案上,會比較安全。它會是你下個專案的建議語言,而非現在專案的首選,這是個人誠心的建議。

「那它到底有多複雜?」

Scala 的複雜性來自於它想要解決許多問題。
在設計 Scala 語言之時,Scala 設計者就比對許多語言的特性,然後嘗試將這些特性加入到 Scala 中。Scala 的企圖心由它的命名 Scalable Language,就可以印證。至於它有多大的企圖心呢?太長了,我們下篇再談。

1. C++ 複雜性與 Java 崛起關係

回想 Java 剛升起的時代,那時 C 是主要的開發語言(現在 C 仍是 TIOBE 調查排行榜的第二名,Java 是第一名),C++ 則是最主要的 Object-Oriented 語言(那時 OO 正風行)。在 C 系列盤據的地盤中,Java 可以脫穎而出,除當時特殊時空背景外,C++ 的複雜性也是造成許多 programmer 轉換跑道到 Java 的原因之一。

關於 C++ 的複雜,這裡不討論,有經驗的 C++ 程式者,想想看光一個 method 宣告,其中 const 放在 method 前、method 後、或是參數前就會有不同的效果,就知道 C++ 如何要求程式開發者的智力與謹慎程度。

更不要說 C++ 物件的產生時機的複雜,讓很多 programmer 摸不著頭緒,「瞭解什麼時候呼叫 constructor,什麼時候呼叫 destructor?」是寫好 C++ 程式的一大關鍵。

雖然 constant、constructor、destructor 都是很正統 Object-Oriented 語言需要處理、且強調的部份。但實務上,你放心讓你同 team 的年輕小朋友處理這麼細膩的小細節嗎?筆者可不敢!

Java 簡化這方面的思緒,object 就是要 new 才會 create 出來,簡單清楚,讓人覺得可親。Garbage collection 也讓人不再擔心 allocated 出來的 memory 會導致未來系統的危機。去除掉 pointer 這個好用又危險的工具,也讓大家鬆了一大口氣,這都是 Java 由 C++ 領域中勝出的一大關鍵因素。

緣起

在「釋迦樂、思家了」原本想要介紹 Scala 的用法,為交代 Scala 的設計背景,需要有許多章節討論這些設計背景。

為避免設計背景的章節影響讀者學習 Scala 的興趣,特將這些章節獨立到本處。