Today I saw someone sharing the following functional code on Weibo. I posted the code below, but I slightly changed the original code. For the functional version, at first glance, it is indeed very impressive. It’s hard to explain. If you take a closer look, you might faint. It seems to be a complete book from heaven. It looks very pretentious, haha. However, I feel that parsing that functional code may be a more interesting process. Moreover, I have written an introductory article on "Functional Programming" before, and I can use this example to sublimate the original article. By the way, this article can better introduce a lot of basic knowledge to you, so I wrote this article.
Let’s look at the code first
This code is unremarkable. It is an O(n) algorithm to find a number from an array. If it cannot find it, it returns null.
The following is the normal old-school way. Needless to say.
//正常的版本 function find (x, y) { for ( let i = 0; i < x.length; i++ ) { if ( x[i] == y ) return i; } return null; } let arr = [0,1,2,3,4,5] console.log(find(arr, 2)) console.log(find(arr, 8))
The result is that the functional expression looks like this (it seems that the above codes are faintly visible below, but it is a little different. In order to eliminate the if language and make it look more like an expression, use ? expression):
//函数式的版本 const find = ( f => f(f) ) ( f => (next => (x, y, i = 0) => ( i >= x.length) ? null : ( x[i] == y ) ? i : next(x, y, i+1))((...args) => (f(f))(...args))) let arr = [0,1,2,3,4,5] console.log(find(arr, 2)) console.log(find(arr, 8))
In order to explain this code clearly, you need to add some knowledge first.
Javascript’s arrow function
First of all, let’s briefly explain the arrow expression introduced by ECMAScript2015. Arrow functions are actually anonymous functions, and their basic syntax is as follows:
(param1, param2, …, paramN) => { statements } (param1, param2, …, paramN) => expression // 等于 : => { return expression; } // 只有一个参数时,括号才可以不加: (singleParam) => { statements } singleParam => { statements } //如果没有参数,就一定要加括号: () => { statements }
Here are some examples:
var simple = a => a > 15 ? 15 : a; simple(16); // 15 simple(10); // 10 let max = (a, b) => a > b ? a : b; // Easy array filtering, mapping, ... var arr = [5, 6, 13, 0, 1, 18, 23]; var sum = arr.reduce((a, b) => a + b); // 66 var even = arr.filter(v => v % 2 == 0); // [6, 0, 18] var double = arr.map(v => v * 2); // [10, 12, 26, 0, 2, 36, 46]
It doesn’t look complicated. However, the first two simple and max examples above both assign the arrow function to a variable, so it has a name. Sometimes, certain functions are called when they are declared, especially in functional programming, when a function also returns an external function. For example, in this example:
function MakePowerFn(power) { return function PowerFn(base) { return Math.pow(base, power); } } power3 = MakePowerFn(3); //制造一个X的3次方的函数 power2 = MakePowerFn(2); //制造一个X的2次方的函数 console.log(power3(10)); //10的3次方 = 1000 console.log(power2(10)); //10的2次方 = 100
In fact, the PowerFn in the MakePowerFn function does not need to be named at all. It can be written as:
function MakePowerFn(power) { return function(base) { return Math.pow(base, power); } }
If you use arrow functions, it can be written as:
MakePowerFn = power => { return base => { return Math.pow(base, power); } }
We can also write More concise (if you use expressions, you don't need { and }, and return statements):
MakePowerFn = power => base => Math.pow(base, power)
I still add parentheses, and Line breaks may make it clearer:
MakePowerFn = (power) => ( (base) => (Math.pow(base, power)) )
Okay, with the above knowledge, we can enter a more advanced topic - recursion of anonymous functions.
Recursion of anonymous functions
Functional programming aims to eliminate stateful functions and for/while loops with function expressions. Therefore, for/while loops should not be used in the world of functional programming. , instead use recursion (the performance of recursion is very poor, so tail recursion is generally used for optimization, that is, the calculation status of the function is passed down layer by layer as parameters, so that the language compiler or The interpreter does not need to use the function stack to help you save the state of the internal variables of the function).
Okay, so, how to do recursion of anonymous functions?
Generally speaking, recursive code means that the function calls itself. For example, our code for finding factorial:
function fact(n){ return n==0 ? 1 : n * fact(n-1); }; result = fact(5);
How to write this recursion under an anonymous function? For anonymous functions, we can pass the anonymous function as a parameter to another function. Because the parameters of the function have names, we can call ourselves. As shown below:
function combinator(func) { func(func); }
Is this a bit suspicious of cheating? Anyway, let’s go further and transform the above function into an arrow function-style anonymous function.
(func) => (func(func))
Now you don’t seem like cheating. Inserting the factorial function above looks like this:
First, reconstruct the fact and remove the name that you call yourself in the fact:
function fact(func, n) { return n==0 ? 1 : n * func(func, n-1); } fact(fact, 5); //输出120
Then, we turn the above version into an arrow function Anonymous function version: var fact = (func, n) => ( n==0 ? 1 : n * func(func, n-1) )
fact(fact, 5)
Here, we still need to use a fact to save this Anonymous function, let's continue, we want the anonymous function to call itself when it is declared.
In other words, we need to treat the function
(func, n) => ( n==0 ? 1 : n * func(func, n-1) )
as a calling parameter and pass it to the following function:
(func, x) => func(func, x)
Finally we get the following code:
( (func, x) => func(func, x) ) ( //函数体 (func, n) => ( n==0 ? 1 : n * func(func, n-1) ), //第一个调用参数 5 //第二调用参数 );
It seems a bit convoluted, anyway, you Do you understand? It's okay, let's continue.
Use the recursion of higher-order functions
But the above recursive anonymous function calls itself, so there are actual parameters of hard code in the code. We want to remove the actual parameters, how to remove them? We can refer to the MakePowerFn example mentioned earlier, but this time it is a recursive version of a higher-order function.
HighOrderFact = function(func){ return function(n){ return n==0 ? 1 : n * func(func)(n-1); }; };
We can see that the above code simply requires a function as a parameter and then returns the recursive version of this function. So, how do we call it?
fact = HighOrderFact(HighOrderFact); fact(5);
连起来写就是:
HighOrderFact ( HighOrderFact ) ( 5 )
但是,这样让用户来调用很不爽,所以,以我们一个函数把 HighOrderFact ( HighOrderFact ) 给代理一下:
fact = function ( hifunc ) { return hifunc ( hifunc ); } ( //调用参数是一个函数 function (func) { return function(n){ return n==0 ? 1 : n * func(func)(n-1); }; } ); fact(5); //于是我们就可以直接使用了
用箭头函数重构一下,是不是简洁了一些?
fact = (highfunc => highfunc ( highfunc ) ) ( func => n => n==0 ? 1 : n * func(func)(n-1) );
上面就是我们最终版的阶乘的函数式代码。
回顾之前的程序
我们再来看那个查找数组的正常程序:
//正常的版本 function find (x, y) { for ( let i = 0; i < x.length; i++ ) { if ( x[i] == y ) return i; } return null; }
先把for干掉,搞成递归版本:
function find (x, y, i=0) { if ( i >= x.length ) return null; if ( x[i] == y ) return i; return find(x, y, i+1); }
然后,写出带实参的匿名函数的版本(注:其中的if代码被重构成了 ?号表达式):
( (func, x, y, i) => func(func, x, y, i) ) ( //函数体 (func, x, y, i=0) => ( i >= x.length ? null : x[i] == y ? i : func (func, x, y, i+1) ), //第一个调用参数 arr, //第二调用参数 2 //第三调用参数 )
最后,引入高阶函数,去除实参:
const find = ( highfunc => highfunc( highfunc ) ) ( func => (x, y, i = 0) => ( i >= x.length ? null : x[i] == y ? i : func (func) (x, y, i+1) ) );
注:函数式编程装逼时一定要用const字符,这表示我写的函数里的状态是 immutable 的,天生骄傲!
再注:我写的这个比原来版的那个简单了很多,原来版本的那个又在函数中套了一套 next, 而且还动用了不定参数,当然,如果你想装逼装到天上的,理论上来说,你可以套N层,呵呵。
现在,你可以体会到,如此逼装的是怎么来的了吧?。
其它
你还别说这就是装逼,简单来说,我们可以使用数学的方式来完成对复杂问题的描述,那怕是递归。其实,这并不是新鲜的东西,这是Alonzo Church 和 Haskell Curry 上世纪30年代提出来的东西,这个就是 Y Combinator 的玩法,关于这个东西,你可以看看下面两篇文章:《The Y Combinator (Slight Return)》,《Wikipedia: Fixed-point combinator》