函数式编程 - 组合compose
函數式編程中有一個比較重要的概念就是函數組合(compose),組合多個函數,同時返回一個新的函數。調用時,組合函數按順序從右向左執行。右邊函數調用后,返回的結果,作為左邊函數的參數傳入,嚴格保證了執行順序,這也是compose 主要特點。
入門簡介
組合兩個函數
compose 非常簡單,通過下面示例代碼,就非常清楚
function compose (f, g) {return function(x) {return f(g(x));} }var arr = [1, 2, 3],reverse = function(x){ return x.reverse()},getFirst = function(x) {return x[0]},compseFunc = compose(getFirst, reverse);compseFunc(arr); // 3參數在函數間就好像通過‘管道’傳輸一樣,最右邊的函數接收外界參數,返回結果傳給左邊的函數,最后輸出結果。
組合任意個函數
上面組合了兩個函數的compose,也讓我們了解了組合的特點,接著我們看看如何組合更多的函數,因為在實際應用中,不會像入門介紹的代碼那么簡單。
主要注意幾個關鍵點:
如此實現,基本沒什么問題,變量arr 在管道中傳入后,經過各種操作,最后返回了結果。
深入理解
認識pipe
函數式編程(FP)里面跟compose類似的方法,就是pipe。
pipe,主要作用也是組合多個函數,稱之為'流', 肯定得按照正常方法,從左往右調用函數,與compose 調用方法相反。
ES6 實現Compose function
先看下compose 最基礎的兩參數版本,
const compose = (f1, f2) => value => f1(f2(value));利用箭頭函數,非常直接的表明兩個函數嵌套執行的關系,
接著看多層嵌套。
(f1, f2, f3...) => value => f1(f2(f3));抽象出來表示:
() => () => result;先提出這些基礎的組合方式,對我們后面理解高級es6方法實現compose有很大幫助。
實現pipe
前面提到pipe 是反向的compose,pipe正向調用也導致它實現起來更容易。
pipe = (...fns) => x => fns.reduce((v, f) => f(v), x)一行代碼就實現了pipe, 套用上面抽象出來的表達式,reduce剛好正向遍歷所有函數, 參數x作為傳遞給函數的初始值, 后面每次f(v)執行的結果,作為下一次f(v)調用的參數v,完成了函數組合調用。
或者,可以把函數組合中,第一個函數獲取參數后,得到的結果,最為reduce遍歷的初始值。
pipe = (fn,...fns) => (x) => fns.reduce( (v, f) => f(v), fn(x));利用es6提供的rest 參數 ,用于獲取函數的多余參數.提取出第一個函數fn,多余函數參數放到fns中,fns可以看成是數組,也不用像arguments那種事先通過Array.prototype.slice.call轉為數組,arguments對性能損耗也可以避免。 fn(x) 第一個函數執行結果作為reduce 初始值。
實現compose
pipe 部分,利用reduce實現,反過來看,compose就可以利用reduceRight
compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);利用遞歸
compose = (fn, ...fns) => fns.length === 0 ? fn: (...args) => fn(compose(...fns)(...args))遞歸代碼,首先看出口條件, fns.length === 0, 最后一定執行最左邊的函數,然后把剩下的函數再經過compose調用,
利用reduce實現。
具體實現代碼點擊這里,一行實現,而且還是用正向的 reduce。
作者其實用例子做了解釋,可以看下reduce 迭代的方向是從左往右的,而compose 要求執行的方向是從從右往左。對數組中每一項執行函數,正常情況下都應該放回執行結果,比如(v, f) => f(v),返回f(v)執行結果,這里是(f, g) => (...args) => f(g(...args))返回一個函數(...args) => f(g(...args)),這樣就可以保證后面的函數g在被作為參數傳入時比前面的函數f先執行。
簡單利用前面的組合兩個函數的例子分析一下。
... composeFunc = compose(getFirst, trace, reverse); composeFunc(arr);主要看reduce 函數里面的執行過程:
- 入口 composeFunc(arr), 第一次迭代,reduce函數執行 (getFirst, trace) => (...args)=>getFirst(trace(...args)),函數(...args)=>getFirst(trace(...args))作為下一次迭代中累計器f的值。
-
第二次迭代,reduce函數中
f == (...args)=>getFirst(trace(...args))g == reverse。// 替換一下 (f, g) => (...args) => f(g(...args)) ((...args)=>getFirst(trace(...args)), reverse) => (...args) => ((...args)=>getFirst(trace(...args)))(reverse(...args)) -
迭代結束,最后得到的comoseFunc就是
// 對照第二次的執行結果, (...args) => f(g(...args))(...args) => ((...args)=>getFirst(trace(...args)))(reverse(...args)) -
調用函數composeFunc(arr)。
(arr) => ((...args)=>getFirst(trace(...args)))(reverse(arr))===》reverse(arr) 執行結果[3, 2, 1] 作為參數((...args)=>getFirst(trace(...args)))([3,2,1])==》入參調用函數getFirst(trace[3,2,1])===》 getFirst([3, 2, 1])===》結果為 3非常巧妙的把后一個函數的執行結果作為包裹著前面函數的空函數的參數,傳入執行。其中大量用到下面的結構
((arg)=> f(arg))(arg) // 轉換一下。(function(x) {return f(x)})(x)
最后
無論是compose, 還是后面提到的pipe,概念非常簡單,都可以使用非常巧妙的方式實現(大部分使用reduce),而且在編程中很大程度上簡化代碼。最后列出優秀框架中使用compose的示例:
- redux/compose
- koa-Compose
- underscorejs/compose
參考鏈接:
- Creating an ES6ish Compose in Javascript
- compose.js
- Optimization-killers
總結
以上是生活随笔為你收集整理的函数式编程 - 组合compose的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何用“区块链+稳定币”技术来提升资产证
- 下一篇: Spring Cloud Alibaba