Skip to main content

Ch6: 函數式程式設計

什麼是函數式程式設計?

  • functional programming 的 function 比較像是數學裡的 function: f(x)
  • 大方向: 在程式變數的賦值上加入規範

定義:

參考至:less-lee-medium

  • Function 必須作為一級公民。意即,
    • 可以像一般變數一般被當作參數傳入
    • 被當作結果輸出
    • 被任意 assign 給其他變數、被任意進行運算。
// 被當作參數傳入
function sayHi() {
console.log('hello')
}

setTimeout(sayHi, 1000)

// after 1 second...
// [LOG]: "hello"
// 被當作結果輸出
function sum(x: number, y: number) {
return x + y
}

function main() {
const tea = 45
const milk = 100

const bill = sum(tea, milk)
// 不會改變原本變數值
console.log('$' + tea)
console.log('$' + milk)
console.log('$' + bill)
}

main()

// [LOG]: "tea: $45"
// [LOG]: "milk: $100"
// [LOG]: "sum: $145"
// 被任意 assign 給其他變數、被任意進行運算。

const sum = function (x: number, y: number) {
return x + y
}

function main() {
const tea = 45
const milk = 100

const getSum = sum

const bill = getSum(tea, milk) + sum(tea, milk)

console.log('tea: $' + tea)
console.log('milk: $' + milk)
console.log('bill: $' + bill)
}

main()

// [LOG]: "tea: $45"
// [LOG]: "milk: $100"
// [LOG]: "bill: $290"
  • Function 中只能有 Expression 而非指令( instructions )。

    詳細看參考

    • Expression 表達式、表示式、運算式,會回傳結果
    • instructions 陳述式,執行一些程式,不會回傳結果
// 回傳 number
function sum(x: number, y: number) {
return x + y
}

// 回傳 void
function hello() {
console.log('hello')
// return void 被忽略
}
  • Function 必須是 「Pure」、沒有 Side Effect。
    • pure function: y = f(x),無論執行多少次、外部如何改變, 永遠對應,只要 input 是 x,output 一定是 y。
    • side effect: 對外部造成影響
// pure function
// 無論何時何地,Output 都只與 Input 有關係

function addOne(x: number) {
return x + 1
}

const y = addOne(2)
console.log(y)

// [LOG]: 3
// side effect function
let count: number = 10

function sideEffectFunc() {
// do something...

count++
}

sideEffectFunc()
console.log('count = ' + count)

// [LOG]: "count = 11"
  • Function 「不可改變 Input 的資料」、「不可 改變狀態」
// 不符合functional programming的原則:
// 可以看到 numbers 這個原始的array被改變了

const numbers = [1, 2, 3, 4, 5]
numbers.splice(0, 1)

console.log('numbers: ' + numbers)

// [LOG]: "numbers: 2,3,4,5"
// 原本的numbers不會被更改,而是產生新的array
const numbers = [1, 2, 3, 4, 5]
const newNumbers = numbers.slice(1, numbers.length)

console.log('numbers: ' + numbers)
console.log('newNumbers: ' + newNumbers)

// [LOG]: "numbers: 1,2,3,4,5"
// [LOG]: "newNumbers: 2,3,4,5"

還有一個例子在前端的 react + redux 中,舊有的狀態永遠不會被改變,而是產生新的狀態 > redux-flow

  • Function 可以任意組合得到新的 Function,且依然滿足以上這些守則
  • Functional Programming 設計哲學之一便是:「以 Function 為最小單位解決問題,任何 Function 都可以任意組合成為新的 Function」。
function add(x: number, y: number) {
return x + y
}

function square(x: number) {
return x * x
}

function compose(f: any, g: any) {
return function (x: number, y: number) {
return f(g(x, y))
}
}

// 先相加,後平方
const addAndSquare = compose(square, add)

const result = addAndSquare(2, 2)

console.log(result)

// [LOG]: 16

函數式程式設計

最主要就是要掌握程式中的變數不可變性

不可變與架構的關係?

為什麼變數可不可變這件事,對架構來說會覺得重要?

  • 以下架構問題都來自於變數可變性:

  • 書:如果沒有變數會被更新,就這些問題就不會發生

  • 如果變數全都不可變,會需要很多儲存空間跟處理速度!在現實世界比較不可行。這時候就要做一些折衷。

折衷辦法:將可變性分離

盡量讓更多處理放到不可變的元件中,與可變的變數分離開來變 sa

  • Transactional Memory:

    • 保護可變的變數避免平行更新與競爭條件的影響。
    • 基於交易或重試的方案來保護這些變數(看不懂)

折衷辦法:事件來源

  • 是一種策略,儲存的只有交易,而不是狀態。當需要取得狀態的時候, 只需要簡單地從源頭開始應用所有交易
  • 此機制不必尋求永久有效,而是在足夠的儲存空間與效能足以讓應用程式在生命週期內工作
  • 銀行餘額的應用程式例子:
    • 原本: 當交易或提款時,會改變原本的餘額變數
    • 改變:
      • 不是儲存餘額,而是儲存每一筆交易(類似 log),而在需要顯示餘額的時候,將所有交易加總。
      • 量太大時,可以在午夜時進行計算並且保存狀態,當需要資訊時,只需要計算來自午夜以來的交易
    • 沒有資料會被更新或刪除 (CRUD => CR),所以不會有平行化問題。

總結

函數式程式設計就是在控制變數不可變性,但是實務上很難完全做到,所以有一些折衷辦法。

  • 三種範式:
    • 結構化程式設計: 在直接控制轉移上加上規範
    • 物件導向程式設計: 在間接控制轉移上加上規範
    • 函數式程式設計: 在變數賦值上加上規範
  • 三種範式都是在限制我們寫程式的方式,哪些是不該做的
  • 軟體就是:循序選擇迭代 (iteration)間接(indirection) 所組成的內容。沒有更多也沒有更少。