오늘 웨이보에서 다음과 같은 기능 코드를 공유하는 것을 보았습니다. 아래 코드를 게시했는데, 기능 버전의 경우 언뜻 보면 이해하기가 매우 어렵습니다. 자세히 보면 기절할 수도 있어요. 완전 하늘에서 내려온 책인 것 같아요. 하하. 하지만 그 기능적 코드를 파싱하는 것이 더 흥미로운 과정이 될 수 있다고 생각합니다. 게다가 이전에 "함수형 프로그래밍"에 대한 소개 기사를 작성한 적이 있는데, 이 예제를 사용하여 원본 기사를 승화시킬 수 있습니다. 많은 기초 지식을 여러분에게 소개하는 것이 좋을 것 같아서 이 글을 썼습니다.
코드를 먼저 보세요
이 코드는 특이하지 않은 배열에서 숫자를 찾는 것입니다. 찾을 수 없으면 null을 반환하세요.
다음은 일반적인 구식 방식입니다. 말할 필요도 없습니다.
//正常的版本 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))
결과적으로 함수식은 이런 모습이 됩니다. (위의 코드들이 아래에 희미하게 보이는 것 같지만 조금 다릅니다. if 언어를 없애기 위해, it be visible ? 표현식을 사용하면 표현식에 더 가깝습니다.
//函数式的版本 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))
이 코드를 명확하게 설명하려면 먼저 몇 가지 지식을 추가해야 합니다.
Javascript의 화살표 함수
먼저 ECMAScript2015에서 도입한 화살표 표현식에 대해 간단히 설명하겠습니다. 화살표 함수는 실제로 익명 함수이며 기본 구문은 다음과 같습니다.
(param1, param2, …, paramN) => { statements } (param1, param2, …, paramN) => expression // 等于 : => { return expression; } // 只有一个参数时,括号才可以不加: (singleParam) => { statements } singleParam => { statements } //如果没有参数,就一定要加括号: () => { statements }
다음은 몇 가지 예입니다.
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]
복잡해 보이지 않습니다. . 그러나 위의 처음 두 개의 단순 예제와 최대 예제는 모두 화살표 함수를 변수에 할당하므로 이름이 있습니다. 때로는 특정 함수가 선언될 때, 특히 함수형 프로그래밍에서 함수가 외부 함수도 반환할 때 호출됩니다. 예를 들어, 다음 예에서는
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
실제로 MakePowerFn 함수의 PowerFn에는 이름을 지정할 필요가 전혀 없습니다.
<로 작성할 수 있습니다. 🎜>
function MakePowerFn(power) { return function(base) { return Math.pow(base, power); } }
MakePowerFn = power => { return base => { return Math.pow(base, power); } }
MakePowerFn => base => Math.pow(base, power)
더 명확하게 하기 위해 괄호와 줄바꿈을 추가하겠습니다.
MakePowerFn = (power) => ( (base) => (Math.pow(base, power)) )
익명 함수의 재귀
그럼 익명함수 재귀는 어떻게 할까요?
일반적으로 재귀 코드는 함수가 자신을 호출하는 경우입니다. 예를 들어 계승을 찾는 코드는 다음과 같습니다.
function fact(n){ return n==0 ? 1 : n * fact(n-1); }; result = fact(5);
function combinator(func) { func(func); }
(func) => (func(func))
이제 바람피울 것 같지 않네요. 위의 계승 함수를 삽입하는 방법은 다음과 같습니다.
먼저 사실을 재구성하고 사실에서 자신이라고 부르는 이름을 제거합니다.
function fact(func, n) { return n==0 ? 1 : n * func(func, n-1); } fact(fact, 5); //输出120
fact(fact, 5)
즉,
(func, n) => ( n==0 ? 1 : n * func(func, n-1) )
(func, x) => func(func, x)
마침내 다음 코드를 얻습니다.
( (func, x) => func(func, x) ) ( //函数体 (func, n) => ( n==0 ? 1 : n * func(func, n-1) ), //第一个调用参数 5 //第二调用参数 );
고차 함수를 이용한 재귀
HighOrderFact = function(func){ return function(n){ return n==0 ? 1 : n * func(func)(n-1); }; };
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》