JavaScriptにおける匿名関数の再帰的呼び出し


どんなプログラミング言語であっても、コードを少し書いたことがある学生は再帰に対してよく分からないと思います。簡単な階乗計算を例に挙げます。

function factorial(n) { 
  if (n <= 1) {
    return 1;
  } else {
    return n * factorial(n-1);
  }
}
再帰的とは、関数内部で自身に対する呼び出しであることを示します。じゃ問題が来ました。Javascriptの中に匿名関数という種類がありますが、名前がないです。どうやって呼びますか?もちろん、匿名関数を定数に割り当てても良いと言ってもいいです。

const factorial = function(n){ 
   if (n <= 1) {
    return 1;
  } else {
    return n * factorial(n-1);
  }
}
もちろんいいです。しかし、いくつかの像については、関数の作成時に自分が明確な変数に価値を与えていることを知らないと、トラブルになります。例えば:

(function(f){
  f(10);
})(function(n){
   if (n <= 1) {
    return 1;
  } else {
    return n * factorial(n-1);//          
  }
})
//Uncaught ReferenceError: factorial is not defined(…)
このような正確な関数名(関数参照変数名)を与える必要が全くない方法が存在しませんか?arguments.callee私たちはどのfunctionの内部にもargmentsという変数にアクセスできることを知っています。(function(){console.dir(arguments)})(1,2)スクリーンショット2016-09-18午後10.53.58
このargments変数の詳細を印刷して、彼はAgmentsの一例であり、データ構造から言えば、彼はクラスの配列であることが分かります。彼は配列の要素メンバーとlength属性のほかに、もう一つのcalee方法があります。このcaleeの方法は何をしますか?MDNを見に来ました
caleeはargmentsオブジェクトの属性です。この関数の関数内では、現在実行中の関数を指すことができます。関数が匿名関数である場合には、名前のない関数式(「匿名関数」とも呼ばれる)など、有用である。
ははは、明らかにこれは私達が欲しいものです。次は:

(function(f){
  console.log(f(10));
})(function(n){
   if (n <= 1) {
    return 1;
  } else {
    return n * arguments.callee(n-1);
  }
})
//output: 3628800
しかし、もう一つの問題があります。MDNの文書には明確に指摘されています。
警告:ECMAScript第5版(ES 5)の厳しいモードでは、argments.callee()の使用は禁止されています。
えっと、ES 5のuse strictにいました。では、ES 6でES 6のarrow functionに変えて書いてみます。

((f) => console.log(f(10)))(
  (n) => n <= 1? 1: arguments.callee(n-1))
//Uncaught ReferenceError: arguments is not defined(…)
ある程度のES 6の基礎を持っている学生は、以前から言いたいと思っていました。矢印関数は簡単な形式の関数表現です。そして、語彙作用領域のthis値を持っています。すなわち、自分の役割領域の下のthis、argments、superとnew.targetなどのオブジェクトは新しく生まれません。
どうすればいいですか?へへへ、私達は少しのFPの思想を助けなければなりませんでした。
Yグループ
Y Commbinatorに関する文章は数え切れないほど多いと言えます。これはヒルベルトの有名な論理学者Haskell B.Curry(Haskell言語は彼が命名したものです。関数式プログラミング言語の中のCurry手法も彼が命名したものです。)から来た組み合わせ演算子(Haskellは研究グループロジックです。commbinary logic)は不思議な魔力があります。与えられたlamda式(関数)の非動点を算出することができます。これにより再帰が可能になる。
ここでは一つの概念を教えなければなりません。
非動点結合子(英語:Fixed-point commbinator、または非動点演算子)は他の関数を計算する非動点の高次関数である。
関数fの非動点は、f(x)=xの値xである。例えば、0と1は関数f(x)=x^2の非動点であり、0^2=0のために1^2=1です。一次関数(単純な値、例えば整数上の関数)の非動点は一次値であり、高次関数fの非動点は別の関数gでf(g)=gとなる。このように、無動点演算子は任意の関数fixであり、どの関数fに対しても存在する。
f(fix(f)=fix(f)不動点結合子は、匿名の再帰関数を定義することを可能にする。それらは再帰的ではないラボラダの抽象を用いて定義することができる。
無類型のlamda演算でよく知られている(一番簡単かもしれない)非動点結合子はYグループ子と呼ばれています。
次に、私たちは一定の演算を通して、このYコンビに推します。

//                       
const fact = (n) => n<=1?1:n*fact(n-1) 
console.log(fact(5)) //120
//            ,               self   
//                         
const fact_gen = (self) => (n) => n<=1?1:n*self(n-1) 
console.log(fact_gen(fact)(5)) //120
//            n,       ,        
const fact1 = (self, n) => n<=1?1:n*self(self, n-1) 
console.log(fact1(fact1, 5)) //120
//    fact1    ,  fact2
const fact2 = (self) => (n) => n<=1?1:n*self(self)(n-1) 
console.log(fact2(fact2)(5)) //120
//        ,     self(self)      
//             : (g)=> n<= 1? 1: n*g(n-1)
const fact3 = (self) => (n) => ((g)=>n <= 1?1:n*g(n-1))(self(self)) 
console.log(fact3(fact3)(5)) //120
// fact3                  ,       
//        n,      n      
//            : (g) => (m) => m<=1?1:m*g(m-1)
const fact4 = (self) => (n) => ((g) => (m) => m<=1?1:m*g(m-1))(self(self))(n) 
console.log(fact4(fact4)(5))
//    fact4  (g) => (m) => m<=1?1:m*g(m-1)    fact_gen
//        ,  fact_gen      ,          
const weirdFunc = (func_gen) => (self) => (n) => func_gen(self(self))(n) 
console.log(weirdFunc(fact_gen)(weirdFunc(fact_gen))(5)) //120
//           Y       
const Y_ = (gen) => (f) => (n)=> gen(f(f))(n)
//           easy 
const factorial = Y_(fact_gen) 
console.log(factorial(factorial)(5)) //120
//      factorial        
//     fact2,fact3,fact4   
//               factorial(5)
//    ,           f' = f(f) = (f)=>f(f)
// eg. const factorial = fact2(fact2)
const Y = gen => n => (f=>f(f))(gen)(n) 
console.log(Y(fact2)(5)) //120 
console.log(Y(fact3)(5)) //120 
console.log(Y(fact4)(5)) //120
ここまで導いてきましたが、背中がスースーと冷えていると感じましたか?とにかく筆者は初めてコントール、ゴデル、トゥーンに触れました。永遠の金色の対角線という文章に触れた時、一瞬にしてこのような数学的な表現の手順を表す方式に圧倒されました。
最後に無定点演算子が得られたかどうかを思い出します。この演算子は高次関数の非動点f(Y(f)=Y(f)を見つけることができます。一つの関数を一つの演算子(関数)に導入して、自分と同じ機能を得ることができますが、自分の関数ではないので、この言い方はちょっと言いにくいですが、味は十分です。
じゃ、最初の問題に戻ります。どうやって匿名関数の再帰を完成しますか?Yの組み合わせがあれば簡単です。

(f => f(f))
(fact => n => n <= 1 ? 1 : n * fact(fact)(n - 1)) 
(5)
// 120
いくつかの説を見たことがありますが、一番がっかりしたのは、それ(Y組子)を導き出すと、それを一目で見て何をしたいのかを言い出せないことです。
締め括りをつける
具体的に言えば、匿名関数の再帰的呼び出しは、日常のjs開発において、本当に少ない。この問題を持ち出して話すと、主にアーグメンントに対するいくつかの解説とY組子に対する概念の普及を引き出したいです。
全部話しましたから、本当に使ったら、どうやって選びますか?来てください。私たちが喜んでいるbenchmarkの下で、それぞれテストします。

// fact 
fact(10) 
// Y
(f => f(f))(fact => n => n <= 1 ? 1 : n * fact(fact)(n - 1))(10)
// Y'
const fix = (f) => f(f) 
const ygen = fix(fact2) 
ygen(10) 
// callee
(function(n) {n<=1?1:n*arguments.callee(n-1)})(10)
環境:Macbook pro(2.5 GHz Intel Core i 7)、node-5.0(V 8:4.6.58)結果:

fact x 18,604,101 ops/sec ±2.22% (88 runs sampled)
Y x 2,799,791 ops/sec ±1.03% (87 runs sampled)
Y' x 3,678,654 ops/sec ±1.57% (77 runs sampled)
callee x 2,632,864 ops/sec ±0.99% (81 runs sampled)
Yとcaleeの性能は同じです。一時的に関数を構築する必要がありますので、直接のfact再帰的呼び出しと同じ数級の違いがあります。無定点関数を算出して保存すると、約倍の性能が上がります。
以上が本文の全部です。本文の内容は皆さんの学習や仕事に一定の助けをもたらしてくれると同時に、私達を応援してください。