如何读懂并写出装逼的函数式代码?
对于函数式的版本,乍一看,的确令人非常费解,仔细看一下,你可能就晕掉了,似乎完全就是天书,看上去非常装逼,哈哈。不过,我感觉解析那段函数式的代码可能会一个比较有趣过程。
先看代码
这个代码平淡无奇,就是从一个数组中找到一个数,O(n)的算法,找不到就返回 null。
下面是正常的 old-school 的方式。不用多说。
![](https://img.haomeiwen.com/i12062597/d5241c91a9349955.png)
结果到了函数式成了下面这个样子(好像上面的那些代码在下面若影若现,不过又有点不太一样,为了消掉if语言,让其看上去更像一个表达式,动用了 ? 号表达式):
![](https://img.haomeiwen.com/i12062597/2957c846881eb56d.png)
为了讲清这个代码,需要先补充一些知识。
Javascript的箭头函数
首先先简单说明一下,ECMAScript2015 引入的箭头表达式。箭头函数其实都是匿名函数,其基本语法如下:
![](https://img.haomeiwen.com/i12062597/a377d09fe7c7d6a9.png)
下面是一些示例:
![](https://img.haomeiwen.com/i12062597/150545828639a6e5.png)
看上去不复杂吧。不过,上面前两个 simple 和 max 的例子都把这箭头函数赋值给了一个变量,于是它就有了一个名字。有时候,某些函数在声明的时候就是调用的时候,尤其是函数式编程中,一个函数还对外返回函数的时候。比如下在这个例子:
![](https://img.haomeiwen.com/i12062597/3601887ff029d8aa.png)
其实,在 MakePowerFn 函数里的那个 PowerFn 根本不需要命名,完全可以写成:
![](https://img.haomeiwen.com/i12062597/29b9d73b1e4558e0.png)
如果用箭头函数,可以写成:
![](https://img.haomeiwen.com/i12062597/053ebe45f7397051.png)
我们还可以写得更简洁(如果用表达式的话,就不需要 { 和 }, 以及 return 语句 ):
![](https://img.haomeiwen.com/i12062597/0e19a593438bee07.png)
我还是加上括号,和换行可能会更清楚一些:
![](https://img.haomeiwen.com/i12062597/c1aa01864fcd34f4.png)
好了,有了上面的知识,我们就可以进入一个更高级的话题——匿名函数的递归。
匿名函数的递归
函数式编程立志于用函数表达式消除有状态的函数,以及for/while循环,所以,在函数式编程的世界里是不应该用for/while循环的,而要改用递归(递归的性能很差,所以,一般是用尾递归来做优化,也就是把函数的计算的状态当成参数一层一层的往下传递,这样语言的编译器或解释器就不需要用函数栈来帮你保存函数的内部变量的状态了)。
好了,那么,匿名函数的递归该怎么做?
一般来说,递归的代码就是函数自己调用自己,比如我们求阶乘的代码:
![](https://img.haomeiwen.com/i12062597/b92a79ff23223606.png)
在匿名函数下,这个递归该怎么写呢?对于匿名函数来说,我们可以把匿名函数当成一个参数传给另外一个函数,因为函数的参数有名字,所以就可以调用自己了。 如下所示:
![](https://img.haomeiwen.com/i12062597/6fad6e0add0bcdf1.png)
这个是不是有点作弊的嫌疑?Anyway,我们再往下,把上面这个函数整成箭头函数式的匿名函数的样子。
![](https://img.haomeiwen.com/i12062597/f6f11062d57fc8cf.png)
现在你似乎就不像作弊了吧。把上面那个求阶乘的函数套进来是这个样子:
首先,先重构一下fact,把fact中自己调用自己的名字去掉:
![](https://img.haomeiwen.com/i12062597/8ffcf095fe7cd9fb.png)
然后,我们再把上面这个版本变成箭头函数的匿名函数版:
![](https://img.haomeiwen.com/i12062597/78b6335de3d5cb12.png)
这里,我们依然还要用一个fact来保存这个匿名函数,我们继续,我们要让匿名函数声明的时候,就自己调用自己。
也就是说,我们要把
![](https://img.haomeiwen.com/i12062597/26c2a5da0f60dc04.png)
这个函数当成调用参数,传给下面这个函数:
![](https://img.haomeiwen.com/i12062597/f1f75733203b1789.png)
最终我们得到下面的代码:
![](https://img.haomeiwen.com/i12062597/cb09afd06972401d.png)
好像有点绕,anyway, 你看懂了吗?没事,我们继续。
动用高阶函数的递归
但是上面这个递归的匿名函数在自己调用自己,所以,代码中有hard code的实参。我们想实参去掉,如何去掉呢?我们可以参考前面说过的那个
![](https://img.haomeiwen.com/i12062597/5189084b06cbc826.png)
我们可以看,上面的代码简单说来就是,需要一个函数做参数,然后返回这个函数的递归版本。那么,我们怎么调用呢?
![](https://img.haomeiwen.com/i12062597/7790ed4563a78662.png)
连起来写就是:
![](https://img.haomeiwen.com/i12062597/5bc21d4ac1744f51.png)
但是,这样让用户来调用很不爽,所以,以我们一个函数把 HighOrderFact ( HighOrderFact ) 给代理一下:
![](https://img.haomeiwen.com/i12062597/3a9fdf3cc87f4bf0.png)
用箭头函数重构一下,是不是简洁了一些?
![](https://img.haomeiwen.com/i12062597/2a9c385a7b95133d.png)
上面就是我们最终版的阶乘的函数式代码。
回顾之前的程序
我们再来看那个查找数组的正常程序:
![](https://img.haomeiwen.com/i12062597/7e5bb6d99ad7b53c.png)
先把for干掉,搞成递归版本:
![](https://img.haomeiwen.com/i12062597/033cde83eeca69fb.png)
然后,写出带实参的匿名函数的版本(注:其中的if代码被重构成了 ?号表达式):
![](https://img.haomeiwen.com/i12062597/d7b850220cfc07a7.png)
最后,引入高阶函数,去除实参:
![](https://img.haomeiwen.com/i12062597/7d7866eb544406da.png)
注:函数式编程装逼时一定要用const字符,这表示我写的函数里的状态是 immutable 的,天生骄傲!
再注:我写的这个比原来版的那个简单了很多,原来版本的那个又在函数中套了一套 next, 而且还动用了不定参数,当然,如果你想装逼装到天上的,理论上来说,你可以套N层,呵呵。
现在,你可以体会到,如此逼装的是怎么来的了吧?
![](https://img.haomeiwen.com/i12062597/e6d72276dd94f39d.png)
转自:https://mp.weixin.qq.com/s/DYTAS5r5tFZyT-JotC4Pkg