当前位置: 首页 > 科技观察

浅谈函数式编程范式_0

时间:2023-03-17 23:07:37 科技观察

背景设想一个场景,如果你需要实现两个这样的函数:transform2:输入一个字符串,输出应该全部转为小写,最后加感叹号。如果按照前面的命令式编程思路,你可能会这样写:consttransform1=(str)=>{if(typeofstr==="string"){return`${str.toUpperCase()}!`;}返回“不是字符串”;};consttransform2=(str)=>{if(typeofstr==="string"){return`${str.toLowerCase()}!`;}return"Notastring";};transform1("helloworld");//"你好世界!"transform2("你好世界");//“你好世界!”两个函数虽然效果不同,但是代码框架非常相似,逻辑冗余死板。更难实现重用。相比之下,函数式编程思想会尝试将逻辑抽象分解成几个可以重用的最小单元。同样的需求可以这样实现:const{flow}=require("lodash/fp");consttoUpper=(str)=>str.toUpperCase();consttoLower=(str)=>str.toLowerCase();constclaim=(str)=>`${str}!`;constisString=(str)=>(typeofstr==="string"?str:"Notastring");consttransform1=flow(isString,toUpper,excuse);consttransform2=flow(isString,toLower,excuse);transform1("helloworld");//"你好世界!"transform2("你好世界");//“你好世界!”一开始可能不需要,但是在中大型项目中特别有用,因为我们不知道以后的需求会变得多复杂。FP使用了大量的函数,每个函数都是一个单独的函数,然后根据功能需求以特定的方式进行组合,编写时易于复用,出现bug时也容易快速定位到相关函数,减少代码重复,易懂,易改,易调试,有弹性。FP(FunctionalProgramming)的核心概念是一种通过简单地组合一组函数来编写程序的风格。它建议我们将几乎所有内容都包装在一个函数中,编写大量可重用的小函数,然后简单地依次调用它们以获得类似的结果:(func1.func2.func3)或组合,例如:func1(func2(func3()))。总而言之:一种抽象思维,一种编程风格,一种编程规范。FP具有以下特点:1.函数是一等公民(first-classcitizen)。这个特性意味着函数与其他数据类型处于平等地位,可以赋值给其他变量或作为参数传递给另一个函数。或者作为其他函数的返回值,js的函数已经具备了这个特性。这也是实现FP的前提。2.DeclarativeProgramming(声明式编程)FP是DeclarativeProgramming的代表。逻辑是用更抽象的代码来理解代码想要实现什么。函数之间不会共享状态(强调什么)。而ImperativeProgramming(命令式编程)更容易写出状态相互依赖的代码(强调how)。取一个totalArray://命令式着重于如何一步步得到结果vararray=[3,2,1]vartotal=0for(vari=0;i<=array.length;i++){total+=array[i]}//OOP//通过封装将状态(数据)和行为(方法)集成到类中,并提供可以在外部访问或操作状态的方法。总计类{构造函数(数字=[]){this._numbers=数字;}calc(){constnumbers=this._numbers;让totle=0;for(leti=0;i{item.type=1;item.age++;})发送HTTPRequestRenderingscreen使用JS方法(eg.splice)将changetheoriginalarray/variableModifyanyexternalvariableDOMoperationtoreadthevalueofinputChangingDBvaluelogging&console:Changethesystemstate4.不可变数据所有数据都是不可变的,这意味着如果你想修改一个对象,你应该createanewobject用于修改,而不是修改现有对象。//mutableconstballs=['篮球','排球','台球']balls[1]='乒乓球';//更改数组balls的原始项//['TableTennis','volleyball','billiards'']//immutableconstballs=['basketball','volleyball','billiards']constnewBalls=[...balls]//复制一份newBalls[1]='TableTennis';balls//['basketball','volleyball','billiards']和原来一样newBalls//['TableTennis','volleyball','billiards']5.Stateless对于一个函数,它不依赖于外部状态完全改变//Statefulconstx=4;x++;//将x更改为5//省略100行...x*2//??忘掉所有关于x//Stateless的东西,不用担心x是什么constx={val:0};constx1=x=>{val:x.val+1};6.PureFunction遵循一输入一输出的原则,同一个值无论输入多少次,输出的结果总是一样的,总会有一个输出值。只做计算和回报回报,不对外界造成任何改变(无副作用)。PureFunction中的数据主要是不可变数据和无状态数据。另外,当一个函数是纯函数,不依赖于任何外部状态,只依赖于函数参数时,也称为引用透明。//impure有副作用constadd=(x,y)=>{console.log(`Adding${x}${y}`)returnx+y}//pureconstadd=(x,y)=>{return{result:x+y,log:`Adding${x}${y}`}}//不纯当n=4时,没有返回值函数tll(i){if(i<3){return0}if(i>5){return1}}//纯函数tll(i){if(i<3){return0}else{return1}}//不纯相同的输入返回值不同letx=1constcount=()=>x++//pureconstcount=(x)=>x+17。柯理化拆分,“合成”合成柯理化的意思是将具有多个参数的多元函数转化为具有较少参数的单元函数的过程。一个简单的curry函数如下所示:constcurry=(fn,length=fn.length,...args)=>args.length>=length?fn(...args):curry.bind(null,fn,length,...args);科力花的作用是固定参数,降低函数的通用性,提高函数的适用性。例如://假设一个通用请求APIconstrequest=(type,url,options)=>...//GETrequestrequest('GET','http://....')//POSTrequestrequest('POST','http://....')//但是通过科丽华,我们可以提取出具体类型的requestconstget=request('GET');get('http://',{..})组合思维一般有两种实现形式,一种是compose:(fa,fb,fc)=>x=>fa(fb(fc(x))),另一种是pipe:(fa,fb,fc)=>x=>fc(fb(fa(x)))compose函数的简单实现:functioncompose(...funcs){if(funcs.length===0){returnarg=>arg;}if(funcs.length===1){returnfuncs[0];}返回函数。reduce((a,b)=>(...args)=>a(b(...args)));}pipe函数的实现同上,只是执行顺序变了:functionpipe(...funcs){if(funcs.length===0){返回参数=>参数;}if(funcs.length===1){returnfuncs[0];}returnfuncs.reduce((a,b)=>(...args)=>b(a(...args)));}介绍lodash与lodash/fp物理结合的意义composition://lodash实现请求数据的处理=>Matryoshka(无科理化)constgetIncompleteTaskSummaries=asyncfunction(memberName){letdata=awaitfetchData();返回排序By(map(reject(filter(get(data,"tasks"),"username"),"complete"),(task)=>pick(task,["id","dueDate","title","priority"])),"dueDate");};//lodash/fp对FP有更好的支持,包括completecuration,data-last等constgetIncompleteTaskSummaries=asyncfunction(memberName){letdata=awaitfetchData();返回compose(sortBy("dueDate"),map(pick(["id","dueDate","title","priority"])),reject("complete"),filter("username"),get(“任务”))(数据);};可以看到,通过物理化学拆分提高了函数的适用性后,通过函数组合,代码变得如此流畅简洁。通过物理化学分解和功能组合,可以很好的发挥FP。实用性大也是FP必不可少的两步操作。物理化后的功能好比加工站,功能组合好比流水线。综上所述,lodash/fp和ramda都具有data-last、物理完备、组合函数、纯函数等有利于FP的特点。但是相比之下,两者还是有些区别的:lodash/fp依赖于lodash,lodash是基于lodash的,偏向于函数式编程。上手容易,但受限于lodash,有很多局限性。Ramda没有前置依赖,完全是FP。整个库贯穿了FP思想,但是入门成本高。Ramda有很多逻辑判断函数(when、ifElse等),而lodash/fp则没有。Ramda有更友好的文档,lodash/fp应该和lodash比较。资料:https://ramdajs.com/docs/https://devdocs.io/lodash~4/indexhttps://github.com/lodash/lodash/wiki/FP-Guide