簡言之就是沒有 side effect (副作用) 的 function,也就是一種純粹無暇的沒有副作用的 function (可稱作 pure function),
而 side effect 就是 function 有做些什麼其他的事情,而不是單純的回傳結果,舉例來說:
What the hell.. 工程師日常工作就是在寫這些東西吧,那我們該怎麼辦?
Funcational Programming 是一種限制我們如何寫程式的風格,而不是程式該長什麼樣子,在 Funcational Programming 中以上 side effect 都有對應其手段去處理,它能幫助我們提高模塊化 (modularity) 程度,可以讓我們在程式中抽離出更多 pure function,讓我們能更好的測試、重用、替換、減少 bug 和多執行序執行等等,日子還久,慢慢一起了解吧。
用買一杯咖啡的時間介紹 FP 的好處。
class Cafe:
def buyCoffee(cc: CreditCard): Coffee =
val cup = Coffee()
cc.charge(cup.price)
cup
Scala 3 的 class 可以讓我們不用寫 new 關鍵字去初始化實例了!
很不幸的這是一個有 side effect 的 function,發生在 cc.charge(cup.price)
這行,對呼叫 buyCoffee 的人來說,你成功得到了一杯咖啡,但實際上 function 偷偷用了你的信用卡去付錢,跟外面的世界有所聯繫,而這些動作就是 side effect, 有 side effect 的壞處之一就是難以測試,
消除 side effect 很簡單,我們除了回傳咖啡物件外,也回傳收費用途的物件,雖然你還是得有個地方去跟信用卡公司付錢,但起碼 Cafe 這個類別不用知道這些,也能讓 Cafe 更好被測試,跟第一個版本比起來,我們不需要 mock CreditCard 這個類別也能測試 buyCoffee function。
class Cafe:
def buyCoffee(cc: CreditCard): (Coffee, Charge) =
val cup = Coffee()
(cup, Charge(cc, cup.price))
前面說到 pure function 就是沒有 side effect 的 function,換句話說,就是一個 function 單純到你給它 A 它就一定回傳你 B,且並不會做任何跟回傳你 B 這件事以外的事情,
舉例來說就像 intToString
這個 function 一樣 (Int => String
),它除了把數字轉為字串外並不會做其他事情。
未來的文章中除非有特別提到,function 這個詞統一隱含是 pure function 的意思。
進一步來說,若我們將 pure function 這個想法公式化,就能得到 Referential Transparency (引用透明性) 概念,這個概念是說,我們可以在程式中,任意地將某一表達式改成其結果,而不會影響到程式運行,
舉例來說 2 + 3
為一個表達式,+
是 pure function,我們可以在任意地方把 2 + 3
改成 5
然後不會影響程式運行。
而用來驗證 function 是不是符合 RT 的方式就是 Substitution Model (替代模式),例如以下的程式:
scala> val x = "Hello, World"
val x: String = Hello, World
scala> val r1 = x.reverse
val r1: String = dlroW ,olleH
scala> val r2 = x.reverse
val r2: String = dlroW ,olleH
scala> r1 == r2
val res0: Boolean = true
當我們把 x 替換成 "Hello, World" 時,其結果也是相同,所以我們可以說 x
符合 RT。
scala> val r1 = "Hello, World".reverse
val r1: String = dlroW ,olleH
scala> val r2 = "Hello, World".reverse
val r2: String = dlroW ,olleH
scala> r1 == r2
val res1: Boolean = true
接下來看一個不符合 RT 的例子,
scala> val x = new StringBuilder("Hello")
val x: StringBuilder = Hello
scala> val y = x.append(", World")
val y: StringBuilder = Hello, World
scala> val r1 = y.toString
val r1: String = Hello, World
scala> val r2 = y.toString
val r2: String = Hello, World
scala> r1 == r2
val res2: Boolean = true
此時 r1 跟 r2 相同,但如果我使用替代模式把 y 替換成 x.append(", World")
會怎麼樣呢?
scala> val r1 = x.append(", World").toString
val r1: String = Hello, World
scala> val r2 = x.append(", World").toString
val r2: String = Hello, World, World
scala> r1 == r2
val res3: Boolean = false
結果不一樣了,因此我們就知道 x.append()
不符合 RT,並不是一個 pure function。
萬變不離其宗,Functional Programming 的能讓程式更易於模組化和易於組合,就像數學表達式那樣不管怎麼替換其結果都相同,也能減少工程師在追蹤變數、撰寫測試的心力,搭配設計模式能幫助您寫出更高內聚和低耦合的程式,可以的話,Keep It Simple and Stupid,盡量保持簡單。