前端必学函数式编程(三)
在前面的文章,我们谈了基础之基础,重要之重要偏函数,偏函数通过函数封装,实现了减少传参数量的目的,解决了手动指定实参的麻烦。
更具重要意义的是:
当函数只有一个形参时,我们能够比较容易地组合它们。这种单元函数,便于进行后续的组合函数;
没错,本篇就是谈关于组合函数。它是函数编程的重中之重之重之重重重!组合函数含义
函数编程就像拼乐高!
乐高有各式各样的零部件,我们将它们组装拼接,拼成一个更大的组件或模型。
函数编程也有各种功能的函数,我们将它们组装拼接,用于实现某个特定的功能。
下面来看一个例子,比如我们要使用这两个函数来分析文本字符串:functionwords(str){returnString(str)。toLowerCase()。split(sb)。filter(functionalpha(v){return〔w〕。test(v);});}functionunique(list){varuniqList〔〕;for(leti0;ilist。length;i){if(uniqList。indexOf(list〔i〕)1){uniqList。push(list〔i〕);}}returnuniqList;}vartextTocomposetwofunctionstogether;varwordsFoundwords(text);varwordsUsedunique(wordsFound);wordsUsed;〔to,compose,two,functions,together〕
不用细看,只用知道:我们先用words函数处理了text,然后用unique函数处理了上一处理的结果wordsFound;
这样的过程就好比生产线上加工商品,流水线加工。
想象一下,如果你是工厂老板,还会怎样优化流程、节约成本?
这里作者给了一种解决方式:去掉传送带!
即减少中间变量,我们可以这样调用:varwordsUsedunique(words(text));wordsUsed
确实,少了中间变量,更加清晰,还能再优化吗?
我们还可以进一步把整个处理流程封装到一个函数内:functionuniqueWords(str){returnunique(words(str));}uniqueWords(text)
这样就像是一个黑盒,无需管里面的流程,只用知道这个盒子输入是什么!输出是什么!输入输出清晰,功能清晰,非常干净!如图:
与此同时,它还能被搬来搬去,或再继续组装。
我们回到uniqueWords()函数的内部,它的数据流也是清晰的:uniqueWordsuniquewordstext封装盒子
上面的封装uniqueWords盒子很nice,如果要不断的封装像uniqueWords的盒子,我们要一个一个的去写吗?functionuniqueWords(str){returnunique(words(str));}functionuniqueWordsA(str){returnuniqueA(wordsA(str));}functionuniqueWordsB(str){returnuniqueB(wordsB(str));}。。。
所以,一切为了偷懒,我们可以写一个功能更加强大的函数来实现自动封装盒子:functioncompose2(fn2,fn1){returnfunctioncomposed(origValue){returnfn2(fn1(origValue));};}ES6箭头函数形式写法varcompose2(fn2,fn1)origValuefn2(fn1(origValue));
接着,调用就变成了这样:varuniqueWordscompose2(unique,words);varuniqueWordsAcompose2(uniqueA,wordsA);varuniqueWordsBcompose2(uniqueB,wordsB);
太清晰了!任意组合
上面,我们组合了两个函数,实际上我们也可以组合N个函数;finalValuefunc1func2。。。funcNorigValue
比如用一个compose函数来实现(敲重点):functioncompose(。。。fns){returnfunctioncomposed(result){拷贝一份保存函数的数组varlistfns。slice();while(list。length0){将最后一个函数从列表尾部拿出并执行它resultlist。pop()(result);}returnresult;};}ES6箭头函数形式写法varcompose(。。。fns)result{varlistfns。slice();while(list。length0){将最后一个函数从列表尾部拿出并执行它resultlist。pop()(result);}returnresult;};
基于前面uniqueWords(。。)的例子,我们进一步再增加一个函数来处理(过滤掉长度小于等于4的字符串):functionskipShortWords(list){varfilteredList〔〕;for(leti0;ilist。length;i){if(list〔i〕。length4){filteredList。push(list〔i〕);}}returnfilteredList;}vartextTocomposetwofunctionstogether;varbiggerWordscompose(skipShortWords,unique,words);varwordsUsedbiggerWords(text);wordsUsed;〔compose,functions,together〕
这样compose函数就有三个入参且都是函数了。我们还可以利用偏函数的特性实现更多:functionskipLongWords(list){。。}varfilterWordspartialRight(compose,unique,words);固定unique函数和words函数varbiggerWordsfilterWords(skipShortWords);varshorterWordsfilterWords(skipLongWords);biggerWords(text);shorterWords(text);
filterWords函数是一个更具有特定功能的变体(根据第一个函数的功能来过滤字符串)。compose变体
compose(。。)函数非常重要,但我们可能不会在生产中使用自己写的compose(。。),而更倾向于使用某个库所提供的方案。了解其底层工作的原理,对我们强化理解函数式编程也非常有用。
我们理解下compose(。。)的另一种变体递归的方式实现:functioncompose(。。。fns){拿出最后两个参数var〔fn1,fn2,。。。rest〕fns。reverse();varcomposedFnfunctioncomposed(。。。args){returnfn2(fn1(。。。args));};if(rest。length0)returncomposedFn;returncompose(。。。rest。reverse(),composedFn);}ES6箭头函数形式写法varcompose(。。。fns){拿出最后两个参数var〔fn1,fn2,。。。rest〕fns。reverse();varcomposedFn(。。。args)fn2(fn1(。。。args));if(rest。length0)returncomposedFn;returncompose(。。。rest。reverse(),composedFn);};
通过递归进行重复的动作比在循环中跟踪运行结果更易懂,这可能需要更多时间去体会;
基于之前的例子,如果我们想让参数反转:varbiggerWordscompose(skipShortWords,unique,words);变成varbiggerWordspipe(words,unique,skipShortWords);
只需要更改compose(。。)内部实现这一句就行:。。。while(list。length0){从列表中取第一个函数并执行resultlist。shift()(result);}。。。
虽然只是颠倒参数顺序,这二者没有本质上的区别。抽象能力
你是否会疑问:什么情况下可以封装成上述的盒子呢?
这就很考验抽象的能力了!
实际上,有两个或多个任务存在公共部分,我们就可以进行封装了。
比如:functionsaveComment(txt){if(txt!){comments〔comments。length〕txt;}}functiontrackEvent(evt){if(evt。name!undefined){events〔evt。name〕evt;}}
就可以抽象封装为:functionstoreData(store,location,value){store〔location〕value;}functionsaveComment(txt){if(txt!){storeData(comments,comments。length,txt);}}functiontrackEvent(evt){if(evt。name!undefined){storeData(events,evt。name,evt);}}
在做这类抽象时,有一个原则是,通常被称作DRY(dontrepeatyourself),即便我们要花时间做这些非必要的工作。
抽象能让你的代码走得更远!比如上例,还能进一步升级:functionconditionallyStoreData(store,location,value,checkFn){if(checkFn(value,store,location)){store〔location〕value;}}functionnotEmpty(val){returnval!;}functionisUndefined(val){returnvalundefined;}functionisPropUndefined(val,obj,prop){returnisUndefined(obj〔prop〕);}functionsaveComment(txt){conditionallyStoreData(comments,comments。length,txt,notEmpty);}functiontrackEvent(evt){conditionallyStoreData(events,evt。name,evt,isPropUndefined);}
这样if语句也被抽象封装了。
抽象是一个过程,程序员将一个名字与潜在的复杂程序片段关联起来,这样该名字就能够被认为代表函数的目的,而不是代表函数如何实现的。通过隐藏无关的细节,抽象降低了概念复杂度,让程序员在任意时间都可以集中注意力在程序内容中的可维护子集上。《程序设计语言》
我们在本系列初始提到:一切为了创造更可读、更易理解的代码。
从另一个角度,抽象就是将命令式代码变成声命式代码的过程。从怎么做转化成是什么。
命令式代码主要关心的是描述怎么做来准确完成一项任务。声明式代码则是描述输出应该是什么,并将具体实现交给其它部分。
比如ES6增加的结构语法:functiongetData(){return〔1,2,3,4,5〕;}命令式vartmpgetData();varatmp〔0〕;varbtmp〔3〕;声明式var〔a,,,b〕getData();
开发者需要对他们程序中每个部分使用恰当的抽象级别保持谨慎,不能太过,也不能不够。阶段小结
函数组合是为了符合声明式编程风格,即关注是什么,而非具体做什么。
它能将一个函数调用的输出路由跳转到另一个函数的调用上,然后一直进行下去,它借助compose(。。)或它的变体实现
我们期望组合中的函数是一元的(输入输出尽量是一个),这个也是前篇有提到的很重要的一个点。
组合声明式数据流是支撑函数式编程其他特性的最重要的工具之一!
以上!我是掘金安东尼:一名人气前端技术博主(文章100w阅读量)
终身写作者(INFP写作人格)
坚持与热爱(简书打卡1000日)
我能陪你一起度过漫长技术岁月吗(以梦为马)
觉得不错,给个点赞和关注吧(这是我最大的动力)b()d
没有选秀权的湖人试训6名新秀!重磅交易将至!谁会离开湖人?湖人四巨头谁走谁留?北京时间6月2日消息,湖人官方推特晒出6名新秀试训照(后附图),今年没有选秀权的湖人这个动作让人浮想联翩,难道重磅交易将至,获得选秀权是选项之一?湖人……
一步错步步错,被利益蒙蔽双眼的张庭,已经判若两人了2021年12月25日,石家庄市场监督管理局通报,上海达尔威贸易有限公司从事传销活动。4天后的凌晨,上海达尔威公司发文回应,表示他们一直合法经营,会配合相关部门的调查工作……
父爱的片段作文父爱是明亮的双眸,指引我们前进,是温柔的话语,呵护我们的心灵。以下是品学网小编为大家整理的关于父爱的片段作文,给大家作为参考,欢迎阅读!父爱的片段作文篇1夜深了,我……
实用的小学毕业作文600字汇总9篇在日常学习、工作和生活中,大家都接触过作文吧,写作文是培养人们的观察力、联想力、想象力、思考力和记忆力的重要手段。还是对作文一筹莫展吗?以下是小编精心整理的小学毕业作文600字……
我期盼的运动会在我看来,运动会,就是为那些跑步快,有运动天赋的人准备的。他们可以在运动会上一展风采,成为全场的焦点!让人羡慕不已!而我们跑得慢的人呢,只能当一名啦啦队队员,给他们加油、鼓气、……
南卡RunnerComm专业通话型骨传导蓝牙耳机都用了哪些黑最近入手了一款南卡RunnerComm,作为专业通话型骨传导蓝牙耳机,产品定位使用场景是商务语音通话。同时,借助南卡在骨传导方面的黑科技核心技术加持,耳机在音乐表现力上也相当不……
我的五一作文500字导语:用一条小小的信息,来表达我小小的心意,用一个快乐的心情,送出我最真诚的祝福。朋友,五一劳动节快乐。下面小编带来了我的五一作文500字,欢迎参考借鉴!我的五一作文500字1……
最爱的广州五年级作文我居住在广州,这是一个风景优美、四季怡人的地方,也是我最爱的地方。春天,是广州赏灯的季节。花城广场以及珠江两岸有灯光节,五光十色的霓虹灯,把整个城市映照得辉煌灿烂;越秀公……
信用卡的竞争拐点增量难存量卷今年要怎样才卷得动信用卡作为零售业务的获客先导,在市场日渐成熟后,已逐步从快速增长的增量市场竞争,过度到精耕存量市场的阶段。仔细梳理2021年银行业财报,不难发现这一趋势……
近3天,北向资金净买入最多30股及净卖出最大30股名单若不喜欢看表格,可输入任何数字进入底部图片区域更新日期:6月1日近3天北向资金净买入最多的30股序号代码名称北向净流入市值增幅占……
樱花树下的约定作文你是否记得,樱花树下的约定。在日本,有个传说,死去的人可以在樱花树下坐着,看着樱花树,要无时无刻的盯着樱花树,也可以走过樱花树来到天堂或地狱。她叫哀尘飘絮,有个究竟……
名单曝光,券商给予买入评级个股数据出炉更新日期:30日收盘因数据较多,压缩了表格,以图片为准!点赞,是莫大的支持!410hr002063hr6。854。581798。77万11。……
Javadoc综合指南按照这些简单的指南生成干净、动态的文档。Javadoc到底是什么?文档是开发人员最好的朋友。但是,该文档最初是如何创建的呢?对于许多Java程序,答案是Javado……
花钱修改IP地址,有什么风险?法律上如何认定?近日,多个社交平台上线IP属地功能,当用户留言评论后,会缀有来自哪里的显示,目前国内IP显示到省份地区,如果用户在国外,则显示到国家,用户无法关闭该功能。随之而来的,是一……
不让世界杯足协霸气安排曝光,中超18队杯赛争冠,CCTV5直聚焦中超CBA,独一无二球迷媒体点击右上角关注,不会后悔的。。。中超赛程的出炉也标志着国内职业联赛的全面开启,经过了漫长等待,中超18队的庐山真面目也将一一揭晓。从……
优卡丹儿童营养软糖,添加了非常丰富的乳矿物盐1。为儿童设计,安全放心,这款软糖,是优卡丹儿童营养零食领域新推出的主力产品,经过专业检测机构检测认证,符合国家食品安全标准,所以在安全性上,妈妈们是完全可以放心的!2。……
旅行租车一定要注意这5点!避免走弯路异地旅行,租车无疑是最方便的出行方式。但为确保安全和免去不必要的麻烦,还应注意几个环节。小编今天整理出来,希望对大家有所裨益。1。旅游租车一定要选择大型正规平台。为……
京花果蜜莳花筑梦创新之花引进品种出新彩!春华秋实探寻京花果蜜之旅北京,是有着三千年历史的国家历史文化名城,世界著名古都。独特的自然地理环境和一代又一代先民的辛勤耕耘,造就了这里多元荟萃的农林业。果树、花卉、蜜蜂……
故宫角楼云天不管哪一天你到角楼去,也不管什么时候段去,那里总会有很多摄影爱好者在那里拍,风吹日晒,日出日落,365天,每天你看到的都有不同的风景,每个人拍出来的角楼都不一样,或许这就是热爱……
鼋头渚之行来源:人民网人民日报海外版每年3月是鼋头渚景区樱花盛开的时节。图为游人在鼋头渚景区游玩赏樱。还月亮摄(人民图片)来江苏省无锡市鼋头渚时,春色尚好,坐在车里,远远看见……
重生随笔说盲区(远和近吴重生摄)吴重生文因疫情原因,我入住海友酒店。此地离我家不过十多分钟的车程,而离天坛东门才一公里多。平时忙碌在熙熙攘攘的人流和车流之间,从未意识到自己离天坛……
秦始皇陵到底有什么?秦始皇陵位于今天陕西省西安市临潼区城东约5公里处,它南倚骊山,北临渭水,总面积相当于78个故宫的大小。整个秦陵仿照当时的京城咸阳修建,分为外城和内城,其中用于埋葬秦始皇的地宫和……
2015年中小学春季开学时间安排2015年中小学春季开学什么时候进行?年底期末将至,很多学校都发出了2015年的寒假的通知了吧,各位家长都很想了解这个2015年中小学春季开学时间是什么时候。各个地方关于……
3岁儿子被老师批评专注力差,博士奶奶教我一招,方法简单实用文忒咪妈妈(原创文章,欢迎个人分享转发)大家好,我是忒咪妈妈如果你要问我,自从当了妈妈之后,觉得什么最重要?我一定会回答你脸皮厚最重要。为啥我会这么说呢?还要……