Javascript Functional Programming

Posted by Benjamin Lu on 2017-08-22

Functional Programming

解決各自的小問題就解決了大問題

FP的好處

大師們怎麼說?

FLOLAC’14 唐鳳 函數設計程式的商業用途 part 1
FLOLAC’14 唐鳳 函數設計程式的商業用途 part 2
FLOLAC’14 唐鳳 函數設計程式的商業用途 part 3
那些 Functional Programming 教我的事 ihower
閃亮亮 Refactoring to Collections , 從陣列重構談物件導向程式設計

小節: FP的好處有

  1. 多線程程式很難寫,原因是都有mutable shared state,純粹的FP中只有immutable變數,本身就滿足thread-safe,提供了多線程程式良好的基底
  2. function可以當作參數傳遞,function也可以當作function的回傳值,很容易基於數學理論寫出更多靈活的函數組合,如curry和合成函數,達到lazy evaluation並省下資源
  3. 操作collection程式碼可讀性佳

Immutable變數

使用Object.freeze

ES5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var o = Object.freeze({
foo: {
greeting: 'Hello'
},
bar: 'world',
baz: '!'
})
// 第二階層的變數依然可以改動
o.foo.greeting = 'Goodbye'
// 第一階層的變數無法改動
o.bar = 'Ben'
// Goodbye, world!
console.log(o.foo.greeting + ', ' + o.bar + o.baz)
// 物件本身被覆蓋了
o = 5

ES6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const o = Object.freeze({
foo: {
greeting: 'Hello'
},
bar: 'world',
baz: '!'
})
// 第二階層的變數依然可以改動
o.foo.greeting = 'Goodbye'
// 第一階層的變數無法改動
o.bar = 'Ben'
// Goodbye, world!
console.log(`${ o.foo.greeting }, ${ o.bar }${o.baz}`)
// 因為const保護,重新賦值失敗TypeError: Assignment to constant variable.
o = 5

都還是有地方能更動,怎麼辦?

Immutable.js來拯救

Function當參數 / Function當回傳

Function當參數的常見場景

  1. setTimeout / setInterval
  2. 後端API
  3. Promise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
setTimeout(function () {
console.log(new Date().getTime())
}, 1000)
router.get('/wallet/addresses', function (req, res, next) {
...
})
// resolve, reject are functions
let myFirstPromise = new Promise((resolve, reject) => {
setTimeout(function(){
resolve("Success!")
}, 250)
})
myFirstPromise.then((successMessage) => {
console.log("Yay! " + successMessage) // Yay! Success!
})

Function當回傳值

最經典的案例是柯里化(Curry)和合成函數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var add = function (x) {
// 固定x參數後回傳一個新的函數
return function (y) {
return x + y
}
}
var increment = add(1)
var addTen = add(10)
increment(2)
// 3
addTen(2)
// 12

合成函數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var compose = function (f, g) {
return function(x) {
return f(g(x))
}
}
var toUpperCase = function (x) {
return x.toUpperCase()
}
var exclaim = function (x) {
return x + '!'
}
var shout = compose(exclaim, toUpperCase)
// SEND IN THE CLOWNS!
shout("send in the clowns")

合成函數的結合率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var compose = function(f, g) {
return function(x) {
return f(g(x))
}
}
var toUpperCase = function(x) {
return x.toUpperCase()
};
var head = function(x) {
return x[0]
}
var reverse = function(x) {
return x.reverse()
}
// 結合率讓兩者test1, test2等價
var test1 = compose(toUpperCase, compose(head, reverse))
var test2 = compose(compose(toUpperCase, head), reverse)
// UPPERCUT
console.log(test1(['jumpkick', 'roundhouse', 'uppercut']))
// UPPERCUT
console.log(test2(['jumpkick', 'roundhouse', 'uppercut']))

相關的FP library:

  1. lodash
  2. ramdajs
  3. underscorejs

操作Collection程式碼可讀性佳

幾個常用FP方法

  1. map()
  2. filter()
  3. reduce()

練習題

找出[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
每個元素乘以3後,大於10且小於20,可以被2整除中,最大值是?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var threeTimes = []
var inInterval = []
var dividedByTwo = []
var max = 0;
for (var i = 0; i < array.length; i++) {
// 乘以3
threeTimes.push(array[i] * 3)
}
for (i = 0; i < threeTimes.length; i++) {
// 過濾大於10且小於20
if ((threeTimes[i] > 10) && (threeTimes[i] < 20)) {
inInterval.push(threeTimes[i])
}
}
for (i = 0; i < inInterval.length; i++) {
// 被2整除
if ((inInterval[i] % 2) === 0) {
dividedByTwo.push(inInterval[i])
}
}
for (i = 0; i < dividedByTwo.length; i++) {
// 找出剩下的資料中最大值
if (max < dividedByTwo[i]) {
max = dividedByTwo[i]
}
}
console.log(max)

邏輯一多就很難一眼看出要做什麼

FP來拯救!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var max = array.map((number) => {
// 乘以3
return number * 3
}).filter((number) => {
// 過濾大於10且小於20
return number > 10 && number < 20
}).filter((number) => {
// 可被2整除
return number % 2 === 0
}).reduce((target, next) => {
// 剩餘最大的
return Math.max(target, next)
})
console.log(max)

是不是一目瞭然!
這樣的做法很常出現在後端API從資料庫中取出collection後做資料再處理

檢驗這樣的程式是不是Immutable?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var max = array.map((number) => {
array = [0] // 加了這行結果一樣嗎?
return number * 3
}).filter((number) => {
return number > 10 && number < 20
}).filter((number) => {
return number % 2 === 0
}).reduce((target, next) => {
return Math.max(target, next)
})
console.log(max) // 18

使用const更嚴謹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用const更嚴謹
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const max = array.map((number) => {
return number * 3
}).filter((number) => {
return number > 10 && number < 20
}).filter((number) => {
return number % 2 === 0
}).reduce((target, next) => {
return Math.max(target, next)
})
console.log(max) // 18

其他FP延伸閱讀資料

Gitbook