今日、Weibo で次の機(jī)能コードを共有している人を見(jiàn)かけました。私は元のコードを少し変更しましたが、一見(jiàn)したところ、それは確かに非常に印象的です。よく見(jiàn)ると気絶してしまいます。まるで天國(guó)からの完全な本のようです(笑)。しかし、その関數(shù)コードを解析することは、より興味深いプロセスである可能性があると感じています。また、以前に「関數(shù)型プログラミング」の入門(mén)記事を書(shū)きましたが、この例を元の記事に昇華することができます?;A(chǔ)知識(shí)をたくさん紹介したほうが良いと思い、この記事を書(shū)きました。
まずコードを見(jiàn)てみましょう
このコードは、配列から數(shù)値を見(jiàn)つけるための O(n) アルゴリズムです。見(jiàn)つからない場(chǎng)合は、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))
その結(jié)果、関數(shù)式は次のようになります(上記のコードが下にうっすら見(jiàn)えているように見(jiàn)えますが、少し異なります。if言語(yǔ)を削除して式のように見(jiàn)せるには、次を使用します) ? 式):
//函數(shù)式的版本 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))
このコードを明確に説明するには、まずいくつかの知識(shí)を追加する必要があります。
JavaScriptのアロー関數(shù)
まずはECMAScript2015で導(dǎo)入されたアロー式について簡(jiǎn)単に説明します。アロー関數(shù)は実際には匿名関數(shù)であり、その基本的な構(gòu)文は次のとおりです:
(param1, param2, …, paramN) => { statements } (param1, param2, …, paramN) => expression // 等于 : => { return expression; } // 只有一個(gè)參數(shù)時(shí),括號(hào)才可以不加: (singleParam) => { statements } singleParam => { statements } //如果沒(méi)有參數(shù),就一定要加括號(hào): () => { 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]
複雑そうには見(jiàn)えません。ただし、上記の最初の 2 つの単純な例と最大の例は両方ともアロー関數(shù)を変數(shù)に割り當(dāng)てているため、名前が付いています。特に関數(shù)プログラミングでは、関數(shù)が外部関數(shù)も返す場(chǎng)合、特定の関數(shù)が宣言されたときに呼び出されることがあります。たとえば、この例では、
function MakePowerFn(power) { return function PowerFn(base) { return Math.pow(base, power); } } power3 = MakePowerFn(3); //制造一個(gè)X的3次方的函數(shù) power2 = MakePowerFn(2); //制造一個(gè)X的2次方的函數(shù) console.log(power3(10)); //10的3次方 = 1000 console.log(power2(10)); //10的2次方 = 100
実際、MakePowerFn 関數(shù)內(nèi)の PowerFn には名前を付ける必要はまったくありません。
function MakePowerFn(power) { return function(base) { return Math.pow(base, power); } }
アロー関數(shù)を使用する場(chǎng)合は、次のように記述できます。
MakePowerFn = power => { return base => { return Math.pow(base, power); } }
さらに簡(jiǎn)潔に書(shū)くこともできます (式を使用する場(chǎng)合は、{ と } と return ステートメントは必要ありません):
MakePowerFn = power => Math.pow(base, power)
ここでもかっこを追加しますが、改行するとわかりやすくなるかもしれません:
MakePowerFn = (power) => ( (base) => (Math.pow(base, power)) )
さて、上記の知識(shí)があれば、より高度なトピックである匿名関數(shù)の再帰に入ることができます。
匿名関數(shù)の再帰
関數(shù)型プログラミングは、関數(shù)式を使用したステートフル関數(shù)と for/while ループを排除することを目的としています。そのため、関數(shù)型プログラミングの世界では、for/while ループを使用するべきではありません。再帰のパフォーマンスは非常に低いため、一般に末尾再帰は最適化に使用されます。つまり、関數(shù)の計(jì)算ステータスがパラメーターとしてレイヤーごとに渡されるため、言語(yǔ)コンパイラーやインタープリターは関數(shù)スタックを使用する必要がありません。関數(shù)の內(nèi)部変數(shù)の狀態(tài)を保存するのに役立ちます)。
それでは、匿名関數(shù)の再帰をどのように行うのでしょうか?
一般に、再帰的コードとは、関數(shù)がそれ自體を呼び出すことを意味します。たとえば、階乗を見(jiàn)つけるためのコード:
function fact(n){ return n==0 ? 1 : n * fact(n-1); }; result = fact(5);
匿名関數(shù)の下でこの再帰を記述するにはどうすればよいですか?匿名関數(shù)の場(chǎng)合、匿名関數(shù)をパラメータとして別の関數(shù)に渡すことができます。関數(shù)のパラメータには名前があるため、自分自身を呼び出すことができます。 以下に示すように:
function combinator(func) { func(func); }
これは少し不正行為の疑いがありますか?とにかく、さらに進(jìn)んで、上記の関數(shù)をアロー関數(shù)スタイルの無(wú)名関數(shù)に変換しましょう。
(func) => (func(func))
今のところ、あなたは浮気しているようには見(jiàn)えません。上記の階乗関數(shù)を挿入すると次のようになります:
まず、ファクトを再構(gòu)築し、ファクト內(nèi)で自分自身と呼ぶ名前を削除します:
function fact(func, n) { return n==0 ? 1 : n * func(func, n-1); } fact(fact, 5); //輸出120
次に、上記のバージョンをアロー関數(shù)に変換します。 匿名関數(shù)のバージョン: var fat = (func, n) => ( n==0 ? 1 : n * func(func, n-1) )
fact(fact, 5)
ここでは、この匿名関數(shù)を保存するためにまだファクトを使用する必要があります。続けましょう。匿名関數(shù)が宣言されたときにそれ自體を呼び出すようにしたい。
言い換えると、関數(shù)
(func, n) => ( n==0 ? 1 : n * func(func, n-1) )
を呼び出しパラメータとして扱い、それを次の関數(shù)に渡す必要があります:
(func, x) => func(func, x)
最後に、次のコードを取得します:
( (func, x) => func(func, x) ) ( //函數(shù)體 (func, n) => ( n==0 ? 1 : n * func(func, n-1) ), //第一個(gè)調(diào)用參數(shù) 5 //第二調(diào)用參數(shù) );
とにかく複雑です、わかりますか?大丈夫、続けましょう。
高階関數(shù)の再帰を使用する
しかし、上記の再帰匿名関數(shù)はそれ自體を呼び出すため、コード內(nèi)にはハードコードの実際のパラメータが存在します。実際のパラメータを削除したいのですが、どうすれば削除できますか?前述の MakePowerFn の例を參照できますが、今回は高階関數(shù)の再帰バージョンです。
HighOrderFact = function(func){ return function(n){ return n==0 ? 1 : n * func(func)(n-1); }; };
上記のコードは単にパラメーターとして関數(shù)を必要とし、この関數(shù)の再帰バージョンを返すことがわかります。では、それを何と呼ぶのでしょうか?
fact = HighOrderFact(HighOrderFact); fact(5);
連起來(lái)寫(xiě)就是:
HighOrderFact ( HighOrderFact ) ( 5 )
但是,這樣讓用戶來(lái)調(diào)用很不爽,所以,以我們一個(gè)函數(shù)把 HighOrderFact ( HighOrderFact ) 給代理一下:
fact = function ( hifunc ) { return hifunc ( hifunc ); } ( //調(diào)用參數(shù)是一個(gè)函數(shù) function (func) { return function(n){ return n==0 ? 1 : n * func(func)(n-1); }; } ); fact(5); //于是我們就可以直接使用了
用箭頭函數(shù)重構(gòu)一下,是不是簡(jiǎn)潔了一些?
fact = (highfunc => highfunc ( highfunc ) ) ( func => n => n==0 ? 1 : n * func(func)(n-1) );
上面就是我們最終版的階乘的函數(shù)式代碼。
回顧之前的程序
我們?cè)賮?lái)看那個(gè)查找數(shù)組的正常程序:
//正常的版本 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); }
然后,寫(xiě)出帶實(shí)參的匿名函數(shù)的版本(注:其中的if代碼被重構(gòu)成了 ?號(hào)表達(dá)式):
( (func, x, y, i) => func(func, x, y, i) ) ( //函數(shù)體 (func, x, y, i=0) => ( i >= x.length ? null : x[i] == y ? i : func (func, x, y, i+1) ), //第一個(gè)調(diào)用參數(shù) arr, //第二調(diào)用參數(shù) 2 //第三調(diào)用參數(shù) )
最后,引入高階函數(shù),去除實(shí)參:
const find = ( highfunc => highfunc( highfunc ) ) ( func => (x, y, i = 0) => ( i >= x.length ? null : x[i] == y ? i : func (func) (x, y, i+1) ) );
注:函數(shù)式編程裝逼時(shí)一定要用const字符,這表示我寫(xiě)的函數(shù)里的狀態(tài)是 immutable 的,天生驕傲!
再注:我寫(xiě)的這個(gè)比原來(lái)版的那個(gè)簡(jiǎn)單了很多,原來(lái)版本的那個(gè)又在函數(shù)中套了一套 next, 而且還動(dòng)用了不定參數(shù),當(dāng)然,如果你想裝逼裝到天上的,理論上來(lái)說(shuō),你可以套N層,呵呵。
現(xiàn)在,你可以體會(huì)到,如此逼裝的是怎么來(lái)的了吧?。
其它
你還別說(shuō)這就是裝逼,簡(jiǎn)單來(lái)說(shuō),我們可以使用數(shù)學(xué)的方式來(lái)完成對(duì)復(fù)雜問(wèn)題的描述,那怕是遞歸。其實(shí),這并不是新鮮的東西,這是Alonzo Church 和 Haskell Curry 上世紀(jì)30年代提出來(lái)的東西,這個(gè)就是 Y Combinator 的玩法,關(guān)于這個(gè)東西,你可以看看下面兩篇文章:《The Y Combinator (Slight Return)》,《Wikipedia: Fixed-point combinator》

ホットAIツール

Undress AI Tool
脫衣畫(huà)像を無(wú)料で

Undresser.AI Undress
リアルなヌード寫(xiě)真を作成する AI 搭載アプリ

AI Clothes Remover
寫(xiě)真から衣服を削除するオンライン AI ツール。

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無(wú)料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡(jiǎn)単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無(wú)料のコードエディター

SublimeText3 中國(guó)語(yǔ)版
中國(guó)語(yǔ)版、とても使いやすい

ゼンドスタジオ 13.0.1
強(qiáng)力な PHP 統(tǒng)合開(kāi)発環(huán)境

ドリームウィーバー CS6
ビジュアル Web 開(kāi)発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)