什麼是 Functional Programming?

什麼是 Functional Programming?

簡言之就是沒有 side effect (副作用) 的 function,也就是一種純粹無暇的沒有副作用的 function (可稱作 pure function),

而 side effect 就是 function 有做些什麼其他的事情,而不是單純的回傳結果,舉例來說:

  • 修改變數值 (Modifying a variable)
  • 修改某處的資料結構 (Modifying a data structure in place)
  • 在物件中設定某值 (Setting a field on an object)
  • 拋出例外或因為錯誤而停止 (Throwing an exception or halting with an error)
  • 在 console 上印東西或讀取使用者輸入 (Printing to the console or reading user input)
  • 讀檔或寫檔 (Reading from or writing to a file)
  • 在螢幕上呈現東西 (Drawing on the screen)

What the hell.. 工程師日常工作就是在寫這些東西吧,那我們該怎麼辦?

Funcational Programming 是一種限制我們如何寫程式的風格,而不是程式該長什麼樣子,在 Funcational Programming 中以上 side effect 都有對應其手段去處理,它能幫助我們提高模塊化 (modularity) 程度,可以讓我們在程式中抽離出更多 pure function,讓我們能更好的測試、重用、替換、減少 bug 和多執行序執行等等,日子還久,慢慢一起了解吧。

FP 的好處

用買一杯咖啡的時間介紹 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?

前面說到 pure function 就是沒有 side effect 的 function,換句話說,就是一個 function 單純到你給它 A 它就一定回傳你 B,且並不會做任何跟回傳你 B 這件事以外的事情,

舉例來說就像 intToString這個 function 一樣 (Int => String),它除了把數字轉為字串外並不會做其他事情。

未來的文章中除非有特別提到,function 這個詞統一隱含是 pure function 的意思。

Referential Transparency 和 Substitution Model

進一步來說,若我們將 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,盡量保持簡單。

tshine73
tshine73
文章: 51

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *