04 | 如何通过组合、管道和reducer让函数抽象化?
04 | 如何通过组合、管道和reducer让函数抽象化?
讲述:石川
时长10:53大小9.94M
组合 Compose
Point-Free
函数组件
独立的组合函数
管道 Pipeline
Unix/Linux 中的管道
JavaScript 中的管道
Transduction
总结
思考题
赞 5
提建议
精选留言(15)
- 鐘2022-09-27 来自北京靜下心來重看一次, 好像看懂了, 以下是我對於 composeReducer 的實作: ``` const { filterTR, mapTR, composeReducer } = (() => { function applyTypeForFunction(fn, type) { fn.type = type; return fn; } function filterTR(fn) { return applyTypeForFunction(fn, "filter"); } function mapTR(fn) { return applyTypeForFunction(fn, "map"); } function composeReducer(inputArray, fnArray) { return inputArray.reduce((sum, element) => { let tmpVal = element; let tmpFn; for (let i = 0; i < fnArray.length; i++) { tmpFn = fnArray[i]; if (tmpFn.type === "filter" && tmpFn(tmpVal) === false) { console.log(`failed to pass filter: ${element} `); return sum; } if (tmpFn.type === "map") { tmpVal = tmpFn(tmpVal); } } console.log(`${element} pass, result = ${tmpVal}`); sum.push(tmpVal); return sum; }, []); } return { filterTR, mapTR, composeReducer }; })(); const isEven = (v) => v % 2 === 0; const passSixty = (v) => v > 60; const double = (v) => 2 * v; const addFive = (v) => v + 5; var oldArray = [36, 29, 18, 7, 46, 53]; var newArray = composeReducer(oldArray, [ filterTR(isEven), mapTR(double), filterTR(passSixty), mapTR(addFive) ]); console.log(newArray); ```展开
作者回复: 帥!!
5 - 卡卡2022-09-27 来自北京我的理解是:reduce可以对原集合的每个元素使用map回调函数进行映射或者使用filter回调函数进行过滤,然后将新值放入新的集合 mapReduce的实现: Array.prototype.mapReduce = function (cb, initValue) { return this.reduce(function (mappedArray, curValue, curIndex, array) { mappedArray[curIndex] = cb.call(initValue, curValue, curIndex, array); return mappedArray; }, []); }; filterReduce的实现: Array.prototype.filterReduce = function (cb, initValue) { return this.reduce(function (mappedArray, curValue, curIndex, array) { if (cb.call(initValue, curValue, curIndex, array)) { mappedArray.push(curValue); } return mappedArray; }, []); };展开
作者回复: 是的,这里利用了reduce的第二个参数的初始值可以是一个“空数组”,映射或过滤后,放入“新数组”。
4 - 深山何处钟2022-09-30 来自北京请问老师,compose那个函数,直接fns后不接reverse,是不是就是pipe的效果呢?
作者回复: 如果不用reverseArgs,pipe是可以简单理解成这样的: var pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
1 - I keep my ideals...2022-09-28 来自北京想请教一下老师compose组合的新函数里面如果有某一个是异步函数,或者没有返回值的情况下该怎么处理呢。还有多条件分支的情况下又该如何处理呢
作者回复: 1. 异步可以考虑结合CPS的promise/then,或 async/await来解决。 2. 没有返回值,可以考虑用Just和Nothing组成Maybe monad。 3. 多条件分支的情况下可以考虑在Maybe monad中创建orElse的方法。
1 - 天择2022-09-27 来自北京最近两篇文章的知识常在框架和库的代码里面见到,也会给我们阅读源码提供帮助。 具体和抽象都是为使用目标服务的,不管是柯里化还是函数组件,都是给使用者提供某种场景下的便利性,只不过有的需要具体的手段,有的需要抽象的手段。
作者回复: 嗯嗯,是这样的,无论具象还是抽象,目的都是学以致用
1 - 海绵薇薇2023-01-12 来自北京我理解使用composeReducer或者管道计算步骤没有变(因为每个函数还是单一功能),区别是composeReducer将计算过程在内部组装好了,并且计算的中间值也是由composeReducer内部进行传递,所以从封装角度来看文章中的案例,composeReducer的封装程度要高于管道。 附上我的composeReducer版本: const filterTR = (condition) => (value) => value.filter(condition); const mapTR = (mapValue) => (value) => value.map(mapValue); function composeReducer(val, reducerlist) { return reducerlist.reduce((total, cur) => cur(total), val); } function isEven(val) { return val % 2 === 0; } function double(val) { return val * 2; } function passSixty(val) { return val > 60; } function addFive(val) { return val + 5; } var oldArray = [36, 29, 18, 7, 46, 53]; var newArray = composeReducer(oldArray, [filterTR(isEven), mapTR(double), filterTR(passSixty), mapTR(addFive)]); console.log(newArray); // 返回:[77,97]展开
- WGH丶2022-12-18 来自海南function compose(...fns) { return fns.reverse().reduce( function reducer(fn1,fn2){ return function composed(...args){ return fn2( fn1( ...args ) ); }; } ); } 老师好,请教下:这里如果不用reverse,且交换下fn1,fn2的执行顺序能达到同样的效果。之所以使用reverse,是为了保证fn1先于fn2执行吗,还是别的原因?展开
作者回复: 这样做的目的就是从右往左reduce哈,另外一种方式就是用reduceRight,这样就不需要reverse了。
- 摆摆2022-12-02 来自北京业务上函数拆这么细会被打吧
作者回复: 在采用某种风格的时候,还是要掌握一个度
1 - 235682022-11-09 来自海南var oldArray = [36, 29, 18, 7, 46, 53]; var newArray = composeReducer(oldArray, [ filterTR(isEven), mapTR(double), filterTR(passSixty), mapTR(addfive), ]); console.log (newArray); // 返回:[77,97] “在这个例子里,我们对一组数组进行了一系列的操作,先是筛选出奇数,再乘以二,之后筛出大于六十的值,最后加上五。在这个过程中,会不断生成中间数组。” 看返回结果是 [77, 97] ,这里好像筛选出来的是奇数吧老师展开
作者回复: 一开始筛的是偶数: 第1次筛出来的偶数是 [36, 18, 46]; 第2次乘以2的数值是 [72, 36, 92]; 第3次筛大于60的是 [72, 92]; 第4次加上5的数值是 [77, 97]。
- 雨中送陈萍萍2022-11-09 来自北京看了下阮老师对PointFree风格的描述(https://www.ruanyifeng.com/blog/2017/03/pointfree.html),可以直接简单理解成对多个运算过程的合成,不涉及到具体值的处理,所以compose和pipeline就是这种风格.
作者回复: 对,compose就是天然的pointfree。
共 2 条评论 - weineel2022-10-25 来自北京经常写函数式代码的时候函数套函数,不知道如何高效调试,不知道老师后面有没有经验分享。
作者回复: 好建议,我先记一下,看看后面有没有机会说到。
- laoergege2022-10-16 来自北京```js function compose(...fns) { return fns.reverse().reduce( function reducer(fn1,fn2){ return function composed(...args){ return fn2( fn1( ...args ) ); }; } ); } ``` 这里 reverse 是不是多余了。。展开
作者回复: 因为这里是compose,不是pipeline,所以需要reverse。
- CondorHero2022-10-15 来自北京Point-Free 这个例子是不是不对,毕竟 x 参数被显示定义了。 // Not point-free - `x` is an explicit argument var isOdd = (x) => equalsToOne(remainderOfTwo(x)); Pointe-Free Style 定义函数时,不显式地指出函数所带参数。这种风格通常需要柯里化或者高阶函数。也叫 Tacit programming。 // 已知: const map = (fn) => (list) => list.map(fn) const add = (a) => (b) => a + b // 所以: // 非Points-Free —— number 是显式参数 const incrementAll = (numbers) => map(add(1))(numbers) // Points-Free —— list 是隐式参数 const incrementAll2 = map(add(1)) incrementAll 识别并且使用了 numbers 参数,因此它不是 Point-Free 风格的。 incrementAll2 仅连接函数与值,并不提及它所使用的参数,因为它是 Point-Free 风格的。 Point-Free 风格的函数就像平常的赋值,不使用 function 或者 =>。展开
作者回复: 这里是说remainderOfTwo和equalsToOne是point-free。isOdd是传参的,所以它不算是point-free。
- 灯火阑珊2022-09-28 来自北京我是从制造业转行的,对pipe和流水线有天然的接受度,上个工序的半成品就是下个工序的入参。
作者回复: 是的,说明生活中的例子无处不在。
- 天择2022-09-27 来自北京point free的理解:把参数去掉,是指参数的含义已经体现在函数声明(名字)里面了,比如equalsToOne,那就是说传入的值是否等于1,如果是equalsToA,那么这个A就得传为参数,加上要比较的x就是两个参数了。这就是所谓“暴露给使用者的就是功能本身”。
作者回复: 是这样的
1