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

看板PLT作者 (墳墓)時間13年前 (2011/01/15 18:12), 編輯推噓1(102)
留言3則, 2人參與, 最新討論串1/11 (看更多)
三、物件導向與 Functional Programming 的甜蜜相遇 選擇的先決條件 -------------- 繼續來說為何 Scala 讓我著迷不已,以至於我鍾情於選擇他做為我的兵刃 吧? Scala 之父 Martin Odersky 曾經將 Scala 歸類為 Postfunctional 的程 式語言,也在眾多的演講中,將 Scala 型容成一個 Unifier,是一個將物 件導向與 Functional Programing 這兩種編程典範結合的程式語言。 其實這樣宣稱的程式語言並不少見--就光譜上比較靠近物件導向和程序導 向這邊的,像是 Python 還有 Ruby 這些程式語言,都支援了一些 Functional Programming 的功能;而光譜另一邊,比較傾向於以 Functional Programming 一端的則有 OCaml 以及微軟的 F# 等。 由於我一開始接觸的程式語言就是 C / Pascal / C++ / Java 這類程序式 導向及物件導向的程式語言,所以在挑選上述眾多的程式語言時,我很自然 地是在物件導向和程序導向這邊尋找。 也因為如此,我不敢說光譜另一端的情況如何。但我必須承認,在靠近物件 導向和程序導向的這一端中,Scala 是我到目前為止,看過最令我驚訝的一 個。 我從來沒想過 Functional Programming 竟然可以和物件導向如此合拍,甚 至,竟然可以透過物件導向的方式讓我去了解 Functional Programming。 畢竟,我當初在學 Haskell 的時候,腦筋根本就轉不過來,一堆東西都無 法理解啊。 為何說我會認為 Scala 是物件導向和 Functional Programming 的完美結 合,以及他可以讓我用物件導向的觀點來看 Functional Programming 呢? 純物件導向與超方便的 Singleton ------------------------------ 首先,Scala 是一個純物件導向的程式語言,甚至讓我有點訝異的純--他 竟然沒有 static / class 變數和方法! 相對的,他提供了超方便的 singleton 物件: ====================== 我是 Scala 程式分隔線 ========================= object MyObject { var count = 0 def addCount() { count += 1 } } MyObject.addCount() MyObject.addCount() println (MyObject.count) ====================================================================== 猜猜看,MyObject 是什麼東西呢?!答案是--物件!而且是 Singleton 物件喲,也就是說整個執行期只會存在一份而已。 其實這樣的做法和原先的 Java 有一些語意的不同,以致於有時會有小小的 不方便,我不知道是不是還有其他的缺點,但我知道的是他有兩項立即的優 點: - 不用再告訴學生什麼是 static 變數和方法了,因為我發現我當助教的 時候,好多人根本無法理解到底什麼是 static 變數。XD - 你不用再試著實作出正確無誤的 Singleton 模式了,只要一個 object 關鍵字 Scala 通通幫你搞定囉。 與物件超級合拍的 High Order Function ------------------------------------ High Order Function 在 Functional Programming 中是非常核心且常用的 技巧,幾乎所有的 Functional Programming 程式中都可以見到這樣的用法 --將某個函數當做另一個函數的參數。 舉例來說,在 Functional Programming 中,如果我們要試著對 (-1, -2, -3, 0, 1, 2, 3) 這個數列裡的每一個原素做平方,最後再找出大於 5 的 有幾個時,做法大致如下: - 宣告一個數列 (-1, -2, -3, 0, 1, 2, 3) - 宣告一個函數,這個函數接受一個整數 n 做為參數,返回整數 n + 1 為結果 - 宣告一個函數,這個函數接受一個整數 n 的參數,n > 5 時為 true,否則為 false - 依序使用 map、filter 這兩個 High Order Function,最後找出結果的數列有多長 以下是 Haskell 版的程式碼(看不懂沒關係,下面有 Python 版的): ====================== 我是 Haskell 程式分隔線 ========================= let xs = [-1, -2, -3, 0, 1, 2, 3] let square n = n * n let isGreaterThan5 n = n > 5 -- 使用 High order function 求解 let result = length (filter isGreaterThan5 (map square xs)) ====================================================================== 當然,既然號稱引入了 Functional Programming 的編程典範,這點小事對 於 Python 也不算是什麼,也可以用類似的方式來求解: ====================== 我是 Python 程式分隔線 ========================= xs = [-1, -2, -3, 0, 1, 2, 3] def square(x): return x*x def isGreaterThan5(x): return x > 5 print len(filter(isGreaterThan5, map(square, xs))) ====================================================================== 如果仔細注意的話,會發現這兩個方式都是以函式為主體,其中的每個函式 都至少有一個參數是要處理的數列,而閱讀最後一行的方式,是必須先找出 最內層的函式呼叫以及他所丟入的參數,再往外一層一層的分析。 或許習慣了 Haskell 之類的 Functional Programming 程式語言的人,可 以很輕鬆的在一長串的函式呼叫中抽絲剝繭,但我發現自己並不善長這樣的 分析。 也因為如此,Scala 的做法是讓我比較習慣的:map、filter、length 這些 東西,是 List 物件的方法。 換句話說,上面的程式,在 Scala 中會長得像下面這樣: ====================== 我是 Scala 程式分隔線 ========================= val xs = List(-1, -2, -3, 0, 1, 2, 3) val square = (n: Int) => n * n val isGreaterThan5 = (n: Int) => n > 5 val result = xs.map(square).filter(isGreaterThan5).length // 上面那行和下面這行等價 // val result = xs.map(n => n * n).filter(n => n > 5).length println (result1) ====================================================================== 如何,如果你也和我已經習慣了物件導向,是不是可以很精楚地說出 result 是怎麼計算出來的呢?!沒錯,直接從左邊往右讀:result 等於 xs 對應 sqaure 再濾出 isGreaterThan5 的東西,最後取得該數列的長度。 這裡值得注意的地方,是你會發現,在 Scala 中 square 和 isGreaterThan5 其實和 Haskell 的版本比較接近--宣告的方式和變數一樣,而不像 Python 版本,是使用宣告函式的語法。 會這樣做的原因其實很簡單--在 Scala 中,函數和 method 是不一樣的, Scala 中的 method 就是單純的 Java method,沒什麼特別的,不過函數的部 份就有趣了,我們等下會仔細研究這部份,現在先佔且擱下。 再繼續前,為了公平起見,我必須提一下。其實不只 Scala 是用這個方式, Ruby 也是將這些常用的 High order function 當做陣列之類的物件的 method, 上面的程式用 Ruby 寫起來可能像這樣: ====================== 我是 Ruby 程式分隔線 ========================= x = [-1, -2, -3, 0, 1, 2, 3] result = x.map{|n| n * n}.select{|x| x > 5}.length puts(result) ====================================================================== 在這邊,因為我不熟 Ruby,所以我只會寫將函數內嵌的版本,但我確定 Ruby 也能寫出和 Scala 一樣將函數放在外面的版本,我曾經看過,不過我忘記怎麼 寫了。 函數也是物件?咁有影?! ------------------------ 剛剛我們有提到,Scala 裡的函數和方法是不一樣的東西,在 Scala 中, 方法就是一般的 Java 物件的方法,那函數又是什麼呢?! 答案是:物件! 是滴,你沒看錯,在 Scala 中函數是單單純純的物件,是一個實做了 FunctionN[T1..TN+1] 這個介面(用 Scala 的術語來講是 Trait)的物件。 上面那個的程式其實可以寫成下面這樣: ====================== 我是 Scala 程式分隔線 ========================= // new 一個這個 class 出來就是函數了,別懷疑 class Square extends Function1[Int, Int] { override def apply(n: Int) = n * n } class IsGreaterThan5 extends Function[Int, Boolean] { override def apply(n: Int) = n > 5 } val xs = List(-1, -2, -3, 0, 1, 2, 3) val result = xs.map(new Square).filter(new IsGreaterThan5).length println (result) ====================================================================== 看見了吧?我覺得這真的是 Scala 讓我嚇到了的一點,沒料到他竟然會用 物件來實作出函數這個東西,而有了上面這樣的對應之後,其實我發現 High order function 忽然之間就變得超級好理解了--不過就是呼叫了參 數物件的 apply 方法嘛! 如何,現在有沒有想到如何自己實作出一個類似 map 的東西呢?其實很簡單: ====================== 我是 Scala 程式分隔線 ========================= /** * 將陣列中的每一個元素都套用 func 一次 * * @param xs 要處理的陣列 * @param func 要套用到陣列上的函數 */ def myMap(xs: Array[Int], func: Function[Int, Int]): Array[Int] = { val result = new Array[Int](xs.length) for (i <- 0 until result.length) { result(i) = func.apply(xs(i)) } return result } val xs = Array(1, 2, 3) val square = (n: Int) => n * n val result = myMap(xs, square) ====================================================================== 怎樣,不知道你有沒有發現,沒想到 High Order Function 這個東西,竟 然可以用物件導向的概念解釋的一清二楚呢?! 至少,我想我到接觸到了 Scala 之後,這才開始了解 High Order Function 到底是怎麼一回事,到底是怎樣運做的。 最後,在這一篇中我只提到了 High Order Function 這一點,不過就如同我 之前所說過的一樣,Scala 是一個純物件導向的程式語言,所以幾乎 Scala 中的所有 Functional Programming 的概念,都是透過物件導向來當做實作 方式的。 除了 High Order Function 外,像是下面幾個在 Functional Programming 中常用的東西,也都是使用物件導向的法式來實現的: - Tuple - Pattern Matching - Monads 總而言之對我而言,Scala 扮演了一個很重要的角色--他用我所習慣的東 西,去解釋另一個我不習慣的世界是如何運作的,而我發現,這個做法對我 來說真的超有效的! 如果你和我一樣始終搞不懂 Functional Programming 的世界到底如何運作, 不妨也來試著玩玩看 Scala,也許也會和我一樣有所斬獲喲! -- ~ 白馬帶著她一步步地回到中原。白馬已經老了,只能慢慢地走, 'v' Brian Hsu 但終是能回到中原的。江南有楊柳、桃花,有燕子、金魚…… // \\ ( 墳 墓 ) /( )\ 但這個美麗的姑娘就像古高昌國人那樣固執。 【白馬嘯西風】 ^`~'^ http://bone.twbbs.org.tw/blog 『那都是很好很好的,可我偏不喜歡。』 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 114.32.42.198

01/22 00:22, , 1F
最後有地方看不懂,你覺得"不過就呼叫apply嘛"來實現高階函數
01/22 00:22, 1F

01/22 00:23, , 2F
但是這句看起來好難理解.
01/22 00:23, 2F

08/11 12:43, , 3F
ruby的話, 改用 @zipCode.fetch("abc") 就會吐KeyError了
08/11 12:43, 3F
文章代碼(AID): #1DCNBuSt (PLT)
討論串 (同標題文章)
以下文章回應了本文 (最舊先):
完整討論串 (本文為第 1 之 11 篇):
文章代碼(AID): #1DCNBuSt (PLT)