1.简介说到函数式编程,大家的第一印象可能是那些学院派的晦涩难懂的代码,充满了很多抽象难懂的符号,似乎只有计算机大学教授使用这些东西。这在某个时代或许是真的,但是近年来,随着技术的发展,函数式编程在实际生产中发挥了巨大的作用,越来越多的语言开始加入闭包、匿名函数等非常典型的函数式编程的特点,在某种程度上,函数式编程正在逐渐“同化”命令式编程。JavaScript是一种典型的多范式编程语言。随着近两年React的流行,函数式编程的概念也开始流行起来。RxJS、cycleJS、lodashJS、underscoreJS等各种开源库都使用了函数式的特性。那么下面就来介绍一下函数式编程的一些知识和概念。2.纯函数如果你还记得初中的一些数学知识,函数f的概念就是对输入x产生一个输出y=f(x)。这是最简单的纯函数。纯函数的定义是对于相同的输入,它总是会得到相同的输出,而且它没有任何可观察到的副作用,也不依赖于外部环境的状态。让我们举个例子。比如在Javascript中,对数组的操作有的是纯的,有的不是:vararr=[1,2,3,4,5];//Array.slice是一个纯函数,因为它没有副作用,对于一个固定输入,输出总是固定的//是的,这个非常实用xs.slice(0,3);//=>[1,2,3]xs.slice(0,3);//=>[1,2,3]//Array.splice是不纯的,它有副作用,对于固定输入,输出不固定//这不是functionalxs.splice(0,3);//=>[1,2,3]xs.splice(0,3);//=>[4,5]xs.splice(0,3);//=>[]在函数式编程中,我们want是像slice这样的纯函数,而不是像splice这样每次调用后都会把数据弄乱的函数。为什么函数式编程拒绝不纯函数?再看一个例子://impurevarmin=18;varcheckage=age=>age>min;//pure,这个很实用varcheckage=age=>age>18;在非纯版本中,checkage函数的行为不仅取决于输入参数age,还取决于外部变量min。也就是说,这个函数的行为需要由外部系统环境来决定。对于大型系统,这种对外部状态的依赖是系统复杂性大大增加的主要原因。可以注意到,purecheckage将18号键位硬编码在函数内部,可扩展性比较差。我们可以在下面的柯里化中看到如何以一种优雅的函数式方式解决这个问题。纯函数不仅可以有效降低系统的复杂度,而且还有很多很棒的特性,比如可缓存性:import_from'lodash';varsin=_.memorize(x=>Math.sin(x));//***第一次计算会慢一点vara=sin(1);//第二次有缓存,速度极快varb=sin(1);三、函数的柯里化functioncurrying(curry)定义很简单:将一些参数传递给一个函数来调用它,并让它返回一个处理剩余参数的函数。例如,对于加法函数varadd=(x,y)=> x+y,我们可以这样柯里化它://EasytoreadES5writingvaradd=function(x){returnfunction(y){returnx+y}}//ES6写法也是比较正统的函数式写法varadd=x=>(y=>x+y);//试试varadd2=add(2);varadd200=add(200);add2(2);//=>4add200(50);//=>250对于像加法这样极其简单的函数,柯里化不是很有用。还记得上面的检查功能吗?我们可以这样柯里化:varcheckage=min=>(age=>age>min);varcheckage18=checkage(18);checkage18(20);//=>true实际上柯里化是一种“预加载”函数的方法。通过传递更少的参数,获得了一个已经记住这些参数的新函数。从某种意义上说,这是一种参数的“缓存”。是一种非常高效的写函数的方式:import{curry}from'lodash';//先curry两个纯函数varmatch=curry((reg,str)=>str.match(reg));varfilter=curry((f,arr)=>arr.filter(f));//判断字符串中是否有空格varhaveSpace=match(/\s+/g);haveSpace("ffffffff");//=>nullhaveSpace("ab");//=>[""]filter(haveSpace,["abcdefg","HelloWorld"]);//=>["Helloworld"]四、函数组合学会使用纯函数以及如何使用之后是咖喱,我们可以很容易地写出这样的“包装”代码:h(g(f(x)));虽然这也是函数式代码,但还是有一定的“不优雅”的感觉。为了解决函数嵌套的问题,我们需要使用“函数组合”://两个函数的组合varcompose=function(f,g){returnfunction(x){returnf(g(x));};};//或者varcompose=(f,g)=>(x=>f(g(x)));varadd1=x=>x+1;varmul5=x=>x*5;compose(mul5,add1)(2);//=>15我们定义的compose就像双面胶,可以把任意两个纯函数组合在一起。当然你也可以扩展出结合三种功能的“三面胶”,甚至是“四面胶”、“N面胶”。这种灵活的组合允许我们像构建块一样组合功能代码:varfirst=arr=>arr[0];varreverse=arr=>arr.reverse();varlast=compose(first,reverse);last([1,2,3,4,5]);//=>55.PointFree具有柯里化和函数组合的基本知识。下面介绍一下PointFree的代码风格。细心的你可能会注意到,在前面的代码中,我们总是喜欢把一些对象自带的方法转换成纯函数:varmap=(f,arr)=>arr.map(f);vartoUpperCase=word=>单词.toUpperCase();这样做是有原因的。PointFree模型没有中文翻译。有兴趣的可以看这里的英文解释:https://en.wikipedia.org/wiki/Tacit_programming如果用中文解释的话,大概就是,不要命名稍纵即逝的中间变量,比如://这不是Piontfreevarf=str=>str.toUpperCase().split('');在这个函数中,我们使用了str作为我们的中间变量,但是这个中间变量不仅让代码变长了,超出了就没有意义了。让我们修改这段代码:vartoUpperCase=word=>word.toUpperCase();varsplit=x=>(str=>str.split(x));varf=compose(split(''),toUpperCase);f("abcdefgh");//=>["ABCD","EFGH"]这种风格可以帮助我们减少不必要的命名,保持代码的简洁和通用。当然,为了在某些函数中写出PointFree风格,代码中的其他地方必须不那么PointFree,这个地方需要自己选择。6.声明式代码和命令式代码命令式代码是指我们写一条一条的指令,让计算机执行一些动作,一般涉及到很多复杂的细节。声明式风格要优雅得多。我们通过编写表达式而不是逐步说明来声明我们想要做什么。//命令式varCEOs=[];for(vari=0;i
