Heute habe ich gesehen, dass jemand den folgenden Funktionscode auf Weibo geteilt hat, aber ich habe den Originalcode leicht geändert. Auf den ersten Blick ist es in der Tat sehr verwirrend Wenn man genau hinschaut, könnte man in Ohnmacht fallen. Es scheint ein komplettes Buch vom Himmel zu sein. Es sieht sehr protzig aus, haha. Ich bin jedoch der Meinung, dass das Parsen dieses Funktionscodes ein interessanterer Prozess sein könnte. Darüber hinaus habe ich bereits einen Einführungsartikel zum Thema „Funktionale Programmierung“ geschrieben und kann dieses Beispiel übrigens verwenden, um den Originalartikel zu sublimieren Es ist besser, Ihnen viel Grundwissen vorzustellen, deshalb habe ich diesen Artikel geschrieben.
Schauen Sie sich zuerst den Code an
Dieser Code dient dazu, eine Zahl aus einem Array zu finden. Es handelt sich um einen O(n)-Algorithmus. Wenn Sie es nicht finden können, geben Sie einfach null zurück.
Das Folgende ist die normale Old-School-Methode. Unnötig zu sagen.
//正常的版本 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))
Das Ergebnis ist, dass der funktionale Ausdruck so aussieht (der obige Code scheint unten schwach sichtbar zu sein, aber er ist etwas anders. Um die if-Sprache zu eliminieren, let Es sieht eher wie ein Ausdruck aus, wenn man den Ausdruck ? verwendet:
//函数式的版本 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))
Um diesen Code klar zu erklären, müssen Sie zunächst etwas Wissen hinzufügen.
Pfeilfunktion von Javascript
Erläutern wir zunächst kurz den von ECMAScript2015 eingeführten Pfeilausdruck. Pfeilfunktionen sind eigentlich anonyme Funktionen und ihre grundlegende Syntax lautet wie folgt:
(param1, param2, …, paramN) => { statements } (param1, param2, …, paramN) => expression // 等于 : => { return expression; } // 只有一个参数时,括号才可以不加: (singleParam) => { statements } singleParam => { statements } //如果没有参数,就一定要加括号: () => { statements }
Hier einige Beispiele:
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]
Es sieht nicht kompliziert aus . Die ersten beiden einfachen und maximalen Beispiele oben weisen jedoch beide die Pfeilfunktion einer Variablen zu, sodass sie einen Namen hat. Manchmal werden bestimmte Funktionen aufgerufen, wenn sie deklariert werden, insbesondere in der funktionalen Programmierung, wenn eine Funktion auch eine externe Funktion zurückgibt. In diesem Beispiel zum Beispiel:
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
Tatsächlich muss der PowerFn in der MakePowerFn-Funktion überhaupt nicht benannt werden. Er kann wie folgt geschrieben werden:
function MakePowerFn(power) { return function(base) { return Math.pow(base, power); } }
Wenn Pfeilfunktionen verwendet werden, kann es wie folgt geschrieben werden:
MakePowerFn = power => { return base => { return Math.pow(base, power); } }
Wir können es auch prägnanter schreiben (wenn Ausdrücke verwendet werden, { und } und Rückgabeanweisungen sind nicht erforderlich):
MakePowerFn = power => base => Math.pow(base, power)
Ich würde immer noch Klammern und Zeilenumbrüche hinzufügen, um es klarer zu machen:
MakePowerFn = (power) => ( (base) => (Math.pow(base, power)) )
Okay, mit dem oben genannten Wissen können wir eine erweiterte Themenrekursion anonymer Funktionen eingeben.
Rekursion anonymer Funktionen
Funktionale Programmierung zielt darauf ab, zustandsbehaftete Funktionen und for/while-Schleifen mit Funktionsausdrücken zu eliminieren, also in Funktionen In der Welt der formalen Programmierung , for/while-Schleifen sollten nicht verwendet werden, sondern stattdessen eine Rekursion (die Leistung der Rekursion ist sehr schlecht, daher wird im Allgemeinen die Schwanzrekursion zur Optimierung verwendet, dh der berechnete Status der Funktion wird als Parameter verwendet). Passen Es wird Schicht für Schicht heruntergefahren, sodass der Sprachcompiler oder Interpreter nicht den Funktionsstapel verwenden muss, um den Status der internen Variablen der Funktion zu speichern.
Okay, wie führt man eine Rekursion anonymer Funktionen durch?
Im Allgemeinen bedeutet rekursiver Code, dass sich die Funktion selbst aufruft. Zum Beispiel unser Code zum Finden von Fakultäten:
function fact(n){ return n==0 ? 1 : n * fact(n-1); }; result = fact(5);
Wie schreibe ich diese Rekursion unter einer anonymen Funktion? Bei anonymen Funktionen können wir die anonyme Funktion als Parameter an eine andere Funktion übergeben. Da die Parameter der Funktion Namen haben, können wir uns selbst aufrufen. Wie unten gezeigt:
function combinator(func) { func(func); }
Ist das ein bisschen betrügerisch verdächtig? Wie auch immer, gehen wir noch einen Schritt weiter und wandeln die obige Funktion in eine anonyme Funktion im Pfeilfunktionsstil um.
(func) => (func(func))
Jetzt sieht es nicht so aus, als ob du schummeln willst. Das Einfügen der obigen Fakultätsfunktion sieht folgendermaßen aus:
Rekonstruieren Sie zunächst die Tatsache und entfernen Sie den Namen, den Sie sich selbst nennen, in der Tatsache:
function fact(func, n) { return n==0 ? 1 : n * func(func, n-1); } fact(fact, 5); //输出120
Dann drehen wir die obige Version um in die anonyme Funktionsversion der Pfeilfunktion: var fact = (func, n) => ( n==0 ? 1 : n * func(func, n-1) )
fact(fact, 5)
Hier müssen wir noch einen Fakt verwenden, um diese anonyme Funktion zu speichern. Wir möchten, dass die anonyme Funktion sich selbst aufruft, wenn sie deklariert wird.
Mit anderen Worten, wir müssen die Funktion
(func, n) => ( n==0 ? 1 : n * func(func, n-1) )
als Aufrufparameter behandeln und an die folgende Funktion übergeben:
(func, x) => func(func, x)
Schließlich erhalten wir den folgenden Code:
( (func, x) => func(func, x) ) ( //函数体 (func, n) => ( n==0 ? 1 : n * func(func, n-1) ), //第一个调用参数 5 //第二调用参数 );
Es scheint jedenfalls etwas kompliziert zu sein, verstehst du? Es ist okay, lass uns weitermachen.
Rekursion mit Funktionen höherer Ordnung
Aber die obige rekursive anonyme Funktion ruft sich selbst auf, sodass der Code tatsächliche Parameter von Hardcode enthält. Wir möchten die tatsächlichen Parameter entfernen. Wie entfernt man sie? Wir können uns auf das zuvor erwähnte MakePowerFn-Beispiel beziehen, aber dieses Mal handelt es sich um eine rekursive Version einer Funktion höherer Ordnung.
HighOrderFact = function(func){ return function(n){ return n==0 ? 1 : n * func(func)(n-1); }; };
Wir können sehen, dass der obige Code einfach eine Funktion als Parameter benötigt und dann die rekursive Version dieser Funktion zurückgibt. Wie nennen wir es also?
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》