[比較] 我為何鍾情於用 Scala 做為兵刃(一、二)

看板PLT (程式語言與理論)作者 (墳墓)時間14年前 (2011/01/15 17:52), 編輯推噓4(401)
留言5則, 4人參與, 最新討論串1/1
一、前言(廢話) 就像我之前所提到的,我覺得程式語言對於寫程式的人來說,就像是兵器一 樣,而行走江湖,有一件趁手的兵刃,往往可以事半功倍。 而兵刃沒有好壞之分,端看用的人如何使用,有的人喜歡大刀的迫力,也有 人鍾情於長劍的輕靈,而不論刀也好,劍也罷,最終的目的都是克敵致勝, 不論用什麼樣的兵器,只有能打退敵人才是真的。 程式語言其實也差不了多少,不論是動態型別的程式語言或靜態型別的程式 語言,不論是物件導向或是 Functional Programming ,最終的目標都是要 解決問題。 只要能解決問題,能克敵致勝,靠他賺得到錢(?),用什麼樣的程式語言 並不是什麼大不了的事情。 所以雖很愛看大家嘴砲各種程式語言的優劣,但其實一直以來都覺得,程式 語言並沒有什麼好壞之分,只不過是每個人的喜好不同罷了。 但另一方面,就像行走江湖的人都有各自偏好的兵刃一般,甚至即使同樣是 長劍,都還有慣用的長短輕重等,我相信寫程式的人也都有自己的偏好以及 喜歡的編程典範。 同時不論是兵器或程式語言,如果手上拿的是自己趁手的東西,往往會對其 產生不同的情感,甚至覺得如果失去了手上的東西,就像斷了手腳一般不自 在。 而這一系列的文章,就是敘述我身為一個寫程式的人,為何在眾多的程式語 言之中,挑選了 Scala 做為了我的兵刃,並且漸漸地愛上了他。 二、靜態型別的動態語言 就如同武林人士一樣,要找到一把適合自己的兵刃,往往不是一天兩天的事 情,我在找到 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 版的實作: ====================== 我是 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 的表現如何唄! ====================== 我是 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 的 表現又如何呢,咱們來看看: ====================== 我是 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, in <module> print (getArea221(True)) File "test.py", line 9, in getArea221 return zipCode["abc"] KeyError: 'abc' ====================================================================== 太棒了,Python 告訴我們有 KeyError 發生,也就是說字串 abc 不能當做 zipCode 的索引。 但是等一下,為什麼前面還是有 Hello World 和『汐止』被印了出來呢? 嗯……因為這是『執行期錯誤』,也就是說如果我們把程式改成下面這樣: ====================== 我是 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)) ====================================================================== 很好,什麼事都不會發生,雖然這份程式碼實際上是有問題的,只要有人呼 叫了 getArea221(True) 他就會在執行的時候毫不留情的爆炸給你看。 不過,會爆炸給你看,似乎總比給你一個 null 或 nil 好得多的樣子?! 最後,我們來看看我最愛的 Scala 長得怎樣: ====================== 我是 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 是要等到執行到那一行的時候才爆炸給你 看。 事實上,就算你把最後一行拿掉,變成下面這樣: ====================== 我是 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、汐止和南港 這三行字,如下所示: ====================== 我是 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 可以在維持程式碼簡短的同時,又幫我抓出類似的錯誤,怎麼能讓我不欣賞他 呢?! 後話: 後來有朋友提出來 Java 會有這個現象是為了保留彈性,雖然我不清楚這邊所謂 的彈性指的是什麼,不過這提醒了我,上面的例子其實沒有完全表現出 Scala 的有趣之處。畢竟 zipCode 就是一個整數對應到字串的 Hash Table 嘛! 而愛用 Ruby / Python 的朋友可能也會想到,Ruby / Python 允許你在同一個 Hash Table 中用不同型態的東西當 Key。 那麼,Scala 又是如何呢?事實上,讓我對 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 裡的東西又會如何呢?! ====================== 我是 Scala 程式分隔線 ========================= 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 ~ $ ====================================================================== 編譯時期的型態錯誤,如何,神奇吧?!這樣的行為真的是深得我心啊,叫我怎 麼能不愛上他呢?! 最後的補充: 後來有一點我發現我忘記提了,為了公平起見還是要提一下--Scala 的型別系 統還是有其極限的。 舉例來說,在上述的例子中,Scala 是試著去找類別樹上能夠包含所有 Map 裡 的鍵的型態的類別,在這個例子是 AnyVal--類似於 Java 的基礎資料型別。 而換句話說,AnyVal 包含了 Boolean 這個型別,也就造成了你可以正確地編譯 下列的程式,但他會在執行期產生錯誤。 ====================== 我是 Scala 程式分隔線 ========================= val map = Map(1 -> "一", 2 -> "二", 2.1 -> "二點一") println (map(1)) println (map(true)) // Runtime Error!! ====================================================================== -- ~ 白馬帶著她一步步地回到中原。白馬已經老了,只能慢慢地走, 'v' Brian Hsu 但終是能回到中原的。江南有楊柳、桃花,有燕子、金魚…… // \\ ( 墳 墓 ) /( )\ 但這個美麗的姑娘就像古高昌國人那樣固執。 【白馬嘯西風】 ^`~'^ http://bone.twbbs.org.tw/blog 『那都是很好很好的,可我偏不喜歡。』 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 114.32.42.198 ※ 編輯: brianhsu 來自: 114.32.42.198 (01/15 17:55)

01/21 04:09, , 1F
看到第 8/20 頁 就 1)
01/21 04:09, 1F

01/21 04:10, , 2F
不過 gentoo給我很亂的感覺 0 0 ;;
01/21 04:10, 2F

01/22 00:07, , 3F
原來如此,真是很有深度的討論
01/22 00:07, 3F

02/04 22:32, , 4F
那如果是型別符合,但是沒有這個key值呢?例如最後例的3
02/04 22:32, 4F

02/13 01:40, , 5F
傷當有意思的討論!
02/13 01:40, 5F
文章代碼(AID): #1DCMvvx3 (PLT)
文章代碼(AID): #1DCMvvx3 (PLT)