該系列會有 3 篇文章,分別介紹什麼是函數式編程、剖析函數式編程庫、以及函數式編程在 React 中的應用,歡迎關注個人 bloghtml
拿泡茶這個事例進行區分命令式編程和聲明式編程git
1.燒開水(爲第一人稱) 2.拿個茶杯 3.放茶葉 4.沖水github
1.給我泡杯茶(爲第二人稱)編程
舉個 demo數組
// 命令式編程
const convert = function(arr) {
const result = []
for (let i = 0; i < arr.length; i++) {
result[i] = arr[i].toLowerCase()
}
return result
}
// 聲明式編程
const convert = function(arr) {
return arr.map(r => r.toLowerCase())
}
複製代碼
函數式編程是聲明式編程的範式。在函數式編程中數據在由純函數組成的管道中傳遞。緩存
函數式編程能夠用簡單如
交換律、結合律、分配律
的數學之法來幫咱們簡化代碼的實現。閉包
它具備以下一些特性:app
// 反面示例
let a = 0
const add = (b) => a = a + b // 兩次 add(1) 結果不一致
// 正確示例
const add = (a, b) => a + b
複製代碼
// 反面示例
const arr = [1, 2]
const arrAdd = (value) => {
arr.push(value)
return arr
}
arrAdd(3) // [1, 2, 3]
arrAdd(3) // [1, 2, 3, 3]
// 正面示例
const arr = [1, 2]
const arrAdd = (value) => {
return arr.concat(value)
}
arrAdd(3) // [1, 2, 3]
arrAdd(3) // [1, 2, 3]
複製代碼
在後記 1 中對數組字符串方法是否對原值有影響做了整理ide
const add = a => b => c => a + b + c
add(1)(2)(3)
複製代碼
const add = a => (b, c) => a + b + c
add(1)(2, 3)
複製代碼
const add = (x) => x + x
const mult = (x) => x * x
const addAndMult = (x) => add(mult(x))
複製代碼
以下是一個加法函數:函數式編程
var add = (a, b, c) => a + b + c
add(1, 2, 3) // 6
複製代碼
假若有這樣一個 curry
函數, 用其包裝 add
函數後返回一個新的函數 curryAdd
, 咱們能夠將參數 a、b
進行分開傳遞進行調用。
var curryAdd = curry(add)
// 如下輸出結果都相同
curryAdd(1, 2, 3) // 6
curryAdd(1, 2)(3) // 6
curryAdd(1)(2)(3) // 6
curryAdd(1)(2, 3) // 6
複製代碼
核心思路: 若傳進去的參數個數未達到 curryAdd
的個數,則將參數緩存在閉包變量 lists 中:
function curry(fn, ...args) {
const length = fn.length
let lists = args || []
let listLen
return function (..._args) {
lists = [...lists, ..._args]
listLen = lists.length
if (listLen < length) {
const that = lists
lists = []
return curry(fn, ...that)
} else if (listLen === length) {
const that = lists
lists = []
return fn.apply(this, that)
}
}
}
複製代碼
如今有 toUpperCase
、reverse
、head
三個函數, 分別以下:
var toUpperCase = (str) => str.toUpperCase()
var reverse = (arr) => arr.reverse()
var head = (arr) => arr[0]
複製代碼
接着使用它們實現將數組末位元素大寫化輸出, 能夠這樣作:
var reverseHeadUpperCase = (arr) => toUpperCase(head(reverse(arr)))
reverseHeadUpperCase(['apple', 'banana', 'peach']) // "PEACH"
複製代碼
此時在構建 reverseHeadUpperCase
函數的時候, 必須手動聲明傳入參數 arr, 是否能提供一個 compose
函數讓使用者更加友好的使用呢? 相似以下形式:
var reverseHeadUpperCase = compose(toUpperCase, head, reverse)
reverseHeadUpperCase(['apple', 'banana', 'peach']) // "PEACH"
複製代碼
此外 compose
函數符合結合律
, 咱們能夠這樣子使用:
compose(compose(toUpperCase, head), reverse)
compose(toUpperCase, compose(head, reverse))
複製代碼
以上兩種寫法與 compose(toUpperCase, head, reverse)
的效果徹底相同, 都是依次從右到左執行傳參中的函數。
此外 compose
和 map
一塊兒使用時也有相關的結合律, 如下兩種寫法效果相等
compose(map(f), map(g))
map(compose(f, g))
複製代碼
代碼精華集中在一行以內, 其爲衆多開源庫(好比 Redux) 所採用。
var compose = (...args) => (initValue) => args.reduceRight((a, c) => c(a), initValue)
複製代碼
範疇論是數學中的一個分支。能夠將範疇理解爲一個容器, 把原來對值的操做,現轉爲對容器的操做。以下圖:
學習函數式編程就是學習各類函子的過程。
函數式編程中, 函子(Functor)
是實現了 map
函數的容器, 下文中將函子視爲範疇,模型可表示以下:
class Functor {
constructor(value) {
this.value = value
}
map(fn) {
return new Functor(fn(this.value))
}
}
複製代碼
可是在函數式編程中, 要避免使用 new
這種面向對象的編程方式, 取而代之對外暴露了一個 of
的接口, 也稱爲 pointed functor
。
Functor.of = value => new Functor(value)
複製代碼
Maybe 函子
是爲了解決 this.value
爲 null 的情形, 用法以下:
Maybe.of(null).map(r => r.toUpperCase()) // null
Maybe.of('m').map(r => r.toUpperCase()) // Maybe {value: "M"}
複製代碼
實現代碼以下:
class Maybe {
constructor(value) {
this.value = value
}
map(fn) {
return this.value ? new Maybe(fn(this.value)) : null
}
}
Maybe.of = value => new Maybe(value)
複製代碼
Either 函子
是爲了對應 if...else...
的語法, 即非左即右
。所以能夠將之拆分爲 Left
和 Right
兩個函子, 它們的用法以下:
Left.of(1).map(r => r + 1) // Left {value: 1}
Right.of(1).map(r => r + 1) // Right {value: 2}
複製代碼
Left 函子
實現代碼以下:
class Left {
constructor(value) {
this.value = value
}
map(fn) {
return this
}
}
Left.of = value => new Left(value)
複製代碼
Right 函子
實現代碼以下(其實就是上面的 Functor
):
class Right {
constructor(value) {
this.value = value
}
map(fn) {
return new Right(fn(this.value))
}
}
Right.of = value => new Right(value)
複製代碼
具體 Either
函數只是對調用 Left 函子
或 Right 函子
做一層篩選, 其接收 f
、g
兩個函數以及一個函子(Left or Right
)
var Either = function(f, g, functor) {
switch(functor.constructor) {
case 'Left':
return f(functor.value)
case 'Right':
return g(functor.value)
default:
return f(functor.value)
}
}
複製代碼
使用 demo:
Either((v) => console.log('left', v), (v) => console.log('def', v), left) // left 1
Either((v) => console.log('rigth', v), (v) => console.log('def', v), rigth) // rigth 2
複製代碼
函子會發生嵌套, 好比下面這樣:
Functor.of(Functor.of(1)) // Functor { value: Functor { value: 1 } }
複製代碼
Monad 函子
對外暴露了 join
和 flatmap
接口, 調用者從而能夠扁平化嵌套的函子。
class Monad {
constructor(value) {
this.value = value
}
map(fn) {
return new Monad(fn(this.value))
}
join() {
return this.value
}
flatmap(fn) {
return this.map(fn).join()
}
}
Monad.of = value => new Monad(value)
複製代碼
使用方法:
// join
Monad.of(Monad.of(1).join()) // Monad { value: 1 }
Monad.of(Monad.of(1)).join() // Monad { value: 1 }
// flatmap
Monad.of(1).flatmap(r => r + 1) // 2
複製代碼
Monad 函子能夠運用在 I/O 這種不純的操做上將之變爲純函數的操做,目前比較懵懂,往後補充。
var test = [1, 2, 3]
var result = test.slice(0, 1)
console.log(test) // [1, 2, 3]
console.log(result) // [1]
複製代碼
var test = [1, 2, 3]
var result = test.concat(4)
console.log(test) // [1, 2, 3]
console.log(result) // [1, 2, 3, 4]
複製代碼
var test = [1, 2, 3]
var result = test.splice(0, 1)
console.log(test) // [2, 3]
console.log(result) // [1]
複製代碼
var arr = [2, 1, 3, 4]
arr.sort((r1, r2) => (r1 - r2))
console.log(arr) // [1, 2, 3, 4]
複製代碼
var test = [1, 2, 3]
var result = test.reverse()
console.log(test) // [3, 2, 1]
console.log(result) // [3, 2, 1]
複製代碼
var test = [1, 2, 3]
var result = test.push(4)
console.log(test) // [1, 2, 3, 4]
console.log(result) // 4
複製代碼
// substr
var test = 'abc'
var result = test.substr(0, 1)
console.log(test) // 'abc'
console.log(result) // a
// substring
var test = 'abc'
var result = test.substring(0, 1)
console.log(test) // 'abc'
console.log(result) // a
// slice
var test = 'abc'
var result = test.slice(0, 1)
console.log(test) // 'abc'
console.log(result) // a
複製代碼