就如同武林人士一樣,要找到一把適合自己的兵刃,往往不是一天兩天的事情,我在找到 Scala 之前,也經常去看各種的程式語言的教學之類的,總希望找到自己中意的程式語言。
在這一系列中,經常會將 Scala 與其他語言做比較,用以說明我為何喜歡上了 Scala ,但其實我並沒有真正在使用這些用來舉例的語言,所以如果其中有什麼謬誤,還請見諒。
另一方面,我也說過了,我覺得程式語言沒有什麼好壞之分,在這邊的舉例只是單純用來說明我喜歡 Scala 的緣由,如果批評到你喜歡的程式語言,同樣也請見諒,我想這只是大家的偏好不同罷了。
首先,我愛上 Scala 的一點,就在於他有著『靜態型別的動態程式語言』這個稱號。
這裡舉一個簡單的例子,我們將會用 Java / Ruby / Python / Scala 四種不同的程式語言來實作,並觀察 Scala 到底有什麼令我愛不釋手的地方。
下面是程式的規格:
- 宣告一個名為 zipCode 的對應表,這個對應表是郵遞區號(整數)到地區(字串)的對應
- 宣告一個 getArea221(shouldCrash) 的函式,這個函式會取得 221 這個郵遞區號的地區
- 如果 shouldCrash 為 false,用整數 221 來當做索引取出 zipCode 相對應的值
- 如果 shouldCrash 為 true,用字串 "abc" 當做索引取出 zipCode 相對應的值
- 當然,既然是郵遞區號,用字串 "abc" 來查詢是完全不合理的
- 在主程式中執行下列下三個步驟
- 印出 Hello World
- 呼叫並印出 getArea221(false) 的值
- 呼叫並印出 getArea221(true) 的值
首先來看一下 Java 版的實作:
// This is Java. import java.util.HashMap; public class Test { static HashMap<Integer, String> zipCode= new HashMap<Integer, String>(); static String getArea221 (boolean shouldCrash) { if (shouldCrash) { return zipCode.get("abc"); } else { return zipCode.get(221); } } public static void main (String [] args) { zipCode.put(221, "汐止"); zipCode.put(115, "南港"); zipCode.put(545, "埔里"); System.out.println("Hello World"); System.out.println(getArea221(false)); System.out.println(getArea221(true)); } }
嗯,看起來就是相當的,嗯……Java,一堆囉哩八唆的規舉和型別,看了就討人厭!
試著編譯一下,可以過關,執行一下,產生了下列的輸出:
brianhsu@NBGentoo ~/test $ java Test Hello World 汐止 null brianhsu@NBGentoo ~/test $
等等!最後一個 null 是怎麼回事咧?我以為 Java 是靜態型別加上強型別的程式語言,應該可以在編譯時期就告訴我,我的程式有型別不符的地方不是嗎?又臭又長卻又沒什麼用,難怪大家討厭 Java,都跑去擁抱 Ruby 和 Python 了。(淚)
好,那我們來看一下最近的後起之秀 Ruby 的表現如何唄!
#!/usr/bin/ruby @zipCode = {221 => "汐止", 115 => "南港", 545 => "埔里"} def getArea221(shouldCrash) if (shouldCrash) @zipCode["abc"] else @zipCode[221] end end puts("Hello World") puts(getArea221(false)) puts(getArea221(true))
嗯,果然不負眾望,整整減少了近十行的程式碼,在簡潔方面,徹徹底底地打敗了 Java 啊!
那在抓出錯誤方面比之 Java 又如何呢?我們來執行這隻程式看看:
brianhsu@NBGentoo ~/test $ ruby test.rb Hello World 汐止 nil brianhsu@NBGentoo ~/test $
喔喔!出現 nil 了!聽說 nil 在 Ruby 裡的地位大概就和 Java 中的 null 一樣,所以看來在這個例子裡,Ruby 在簡潔方面的表現勝出,但抓錯誤方面好像半斤八兩。
那麼這個月衝上 TIOBE Index 第五名,大家都說相當簡潔易學的 Python 的表現又如何呢,咱們來看看:
#!/usr/bin/python #coding=utf-8 zipCode = {221: "汐止", 115: "南港", 545: "埔里"} def getArea221(shouldCrash): if shouldCrash: return zipCode["abc"] else: return zipCode[221] print ("Hello World") print (getArea221(False)) print (getArea221(True))
大家的說法不是沒有道理的,基本上看起來和 Ruby 長得差不多,都很簡潔,所以在簡潔性方面也是大勝 Java。
不過不知道 Python 是不是能抓出這個程式碼不合理的地方呢?來試試看好了:
Hello World 汐止 Traceback (most recent call last): File "test.py", line 16, inprint (getArea221(True)) File "test.py", line 9, in getArea221 return zipCode["abc"] KeyError: 'abc'
太棒了,Python 告訴我們有 KeyError 發生,也就是說字串 abc 不能當做 zipCode 的索引。
但是等一下,為什麼前面還是有 Hello World 和『汐止』被印了出來呢?嗯……因為這是『執行期錯誤』,也就是說,如果我們把程式改成下面這樣:
#!/usr/bin/python #coding=utf-8 zipCode = {221: "汐止", 115: "南港", 545: "埔里"} def getArea221(shouldCrash): if shouldCrash: return zipCode["abc"] else: return zipCode[221] print ("Hello World") print (getArea221(False))
很好,什麼事都不會發生,雖然這份程式碼實際上是有問題的,只要有人呼叫了 getArea221(True) 他就會在執行的時候毫不留情的爆炸給你看。
不過,會爆炸給你看,似乎總比給你一個 null 或 nil 好得多的樣子?!
最後,我們來看看我最愛的 Scala 長得怎樣:
// This is Scala. val zipCode = Map(221 -> "汐止", 115 -> "南港", 545 -> "埔里") def getArea221(shouldCrash: Boolean) = { if (shouldCrash) { zipCode("abc") } else { zipCode(221) } } println ("Hello World") println (getArea221(false)) println (getArea221(true))
『呃……十五行?!你真的確定這是靜態型別的程式語言嗎?!』
沒錯,看起來很不可思議吧?!和 Ruby / Python 版的看起來差不多,唯一看到的型別好像也只有 Boolean 而已咧?!
如果不相信,我們來執行一下:
brianhsu@NBGentoo ~/test $ scala test.scala /home/brianhsu/test/test.scala:7: error: type mismatch; found : java.lang.String("abc") required: Int zipCode("abc") ^ one error found brianhsu@NBGentoo ~/test $
呃,test.scala 第七行有型別錯誤,找到的是 java.lang.String,但需求的是 Int,這一行程式碼是 zipCode("abc")!
注意!Hello World 沒有被印出來,也就是說,這隻程式在還沒執行的時候就被發現錯誤了!而不像 Python 是要等到執行到那一行的時候才爆炸給你看。
事實上,就算你把最後一行拿掉,變成下面這樣:
// This is Scala. val zipCode = Map(221 -> "汐止", 115 -> "南港", 545 -> "埔里") def getArea221(shouldCrash: Boolean) = { if (shouldCrash) { zipCode("abc") } else { zipCode(221) } } println ("Hello World") println (getArea221(false))
他一樣會爆給你看,證明了 Scala 確實是靜態型別的程式語言--在執行前,會先進行型別檢查,確定型別都沒問題之後,才會真的開始執行。
brianhsu@NBGentoo ~/test $ scala test.scala /home/brianhsu/test/test.scala:7: error: type mismatch; found : java.lang.String("abc") required: Int zipCode("abc") ^ one error found brianhsu@NBGentoo ~/test $
但如果我把上面的程式碼中的 abc 改成整數 115,那程式就可以正常執行了,同時執行的結果也會一如預期的是分別印出 Hello World、汐止和南港這三行字,如下所示:
// This is Scala. val zipCode = Map(221 -> "汐止", 115 -> "南港", 545 -> "埔里") def getArea221(shouldCrash: Boolean) = { if (shouldCrash) { zipCode(115) } else { zipCode(221) } } println ("Hello World") println (getArea221(false)) println (getArea221(true))
brianhsu@NBGentoo ~/test $ scala test.scala Hello World 汐止 南港 brianhsu@NBGentoo ~/test $
如何,是不是很神奇呢?這也是我喜歡上 Scala 的一個原因--他或許比 Ruby / Python 這些動態語言多了一些型別標示(主要是在函式宣告的部份),但看起來仍然比 Java 簡潔很多,但同時又是靜態型別與強型別的程式語言。
這對我來說是相當重要的一點,因為我很清楚我是個粗心的人,之前寫 PHP 時常常 debug 半天,結果卻發現只是因為我把變數或函數的名稱打錯,而 Scala 可以在維持程式碼簡短的同時,又幫我抓出類似的錯誤,怎麼能讓我不欣賞他呢?!
Update 2010/01/12 17:41:
下面有朋友提出來 Java 此舉是為了保留彈性,雖然我不清楚這邊所謂的彈性指的是什麼,不過這提醒了我,上面的例子其實沒有完全表現出 Scala 的有趣之處,畢竟 zipCode 就是一個整數對應到字串的 Hash Table 嘛!而愛用 Ruby / Python 的朋友可能也會想到,Ruby / Python 允許你在同一個 Hash Table 中用不同型態的東西當 Key。
那麼,Scala 又是如何呢?事實上,讓我對 Scala 感到驚豔的地方,就在於他的行為通常都合理到讓你覺得不可思議,常常就是你要的東西。
例如,下列的 Scala 程式碼是合法的:
val map = Map(1 -> "一", 2 -> "二", 2.1 -> "二點一") println (map(1)) println(map(2)) println(map(2.1))
執行結果:
brianhsu@NBGentoo ~ $ scala test.scala 一 二 二點一 brianhsu@NBGentoo ~ $
看吧!Scala 也可以用不同的型態當做鍵值喲!那如果在這個範例中,試著用字串來取出 map 裡的東西又會如何呢?!
val map = Map(1 -> "一", 2 -> "二", 2.1 -> "二點一") println (map(1)) println (map("我是字串"))
他會告訴你……
brianhsu@NBGentoo ~ $ scala test.scala /home/brianhsu/test.scala:3: error: type mismatch; found : java.lang.String("我是字串") required: AnyVal println (map("我是字串")) ^ one error found brianhsu@NBGentoo ~ $
編譯時期的型態錯誤,如何,神奇吧?!這樣的行為真的是深得我心啊,叫我怎麼能不愛上他呢?!
回響