web-dev-qa-db-ja.com

ES6でアロー関数を再帰的に作成するにはどうすればよいですか?

ES6の矢印関数にはargumentsプロパティがないため、arguments.calleeは機能せず、匿名モードだけが使用されている場合でも、厳密モードでは機能しません。

アロー関数には名前を付けることができないため、名前付きの関数式のトリックは使用できません。

それで...再帰的な矢印関数をどのように書くのですか?これは、特定の条件などに基づいて自分自身を再帰的に呼び出すアロー関数ですか?

39
Siddharth

名前を付けずに再帰関数を作成することは、コンピューターサイエンス自体と同じくらい古い問題です(実際にはλ-calculusはコンピューターサイエンスよりも古いため、λ-calculusall関数は匿名ですが、まだ再帰が必要です。

解決策は、固定小数点コンビネーター、通常はYコンビネーターを使用することです。これは次のようになります。

(y => 
  y(
    givenFact => 
      n => 
        n < 2 ? 1 : n * givenFact(n-1)
  )(5)
)(le => 
  (f => 
    f(f)
  )(f => 
    le(x => (f(f))(x))
  )
);

これは、5の階乗を再帰的に計算します。

注:コードはこれに大きく基づいています: Y CombinatorはJavaScriptで説明されています 。すべてのクレジットは元の作者に送られるべきです。私はほとんど「調和」している(ES /ハーモニーの新機能を備えた古いコードをリファクタリングと呼んでいますか?)それ。

30
Jörg W Mittag

esdiscuss.org Webサイト のディスカッションで、クラウスレインケがあなたの質問に回答しました。

ES6では、彼が再帰コンビネーターと呼ぶものを定義する必要があります。

 let rec = (f)=> (..args)=> f( (..args)=>rec(f)(..args), ..args )

再帰的なアロー関数を呼び出す場合は、パラメーターとしてアロー関数を使用して再帰コンビネーターを呼び出す必要があります。アロー関数の最初のパラメーターは再帰関数で、残りはパラメーターです。再帰関数の名前は、再帰コンビネーターの外では使用されないため、重要ではありません。その後、匿名の矢印関数を呼び出すことができます。ここでは、6の階乗を計算します。

 rec( (f,n) => (n>1 ? n*f(n-1) : n) )(6)

Firefoxでテストする場合は、再帰コンビネーターのES5変換を使用する必要があります。

function rec(f){ 
    return function(){
        return f.apply(this,[
                               function(){
                                  return rec(f).apply(this,arguments);
                                }
                            ].concat(Array.prototype.slice.call(arguments))
                      );
    }
}
15
Ortomala Lokni

矢印関数を変数に割り当て、それを使用して関数を再帰的に呼び出すことができるようです。

var complex = (a, b) => {
    if (a > b) {
        return a;
    } else {
        complex(a, b);
    }
};
11
user405398

関数を割り当てる変数を使用します。

const fac = (n) => n>0 ? n*fac(n-1) : 1;

本当に匿名が必要な場合は、次のように Y Combinator を使用します。

const Y = (f) => ((x)=>f((v)=>x(x)(v)))((x)=>f((v)=>x(x)(v)))
… Y((fac)=>(n)=> n>0 ? n*fac(n-1) : 1) …

醜いですね

10
Bergi

任意の数の引数を再帰的に定義するための汎用コンビネーター(それ自体の中で変数を使用せずに)は次のようになります。

const rec = (le => ((f => f(f))(f => (le((...x) => f(f)(...x))))));

これは、たとえば階乗を定義するために使用できます。

const factorial = rec( fact => (n => n < 2 ? 1 : n * fact(n - 1)) );
//factorial(5): 120

または文字列の逆:

const reverse = rec(
  rev => (
    (w, start) => typeof(start) === "string" 
                ? (!w ? start : rev(w.substring(1), w[0] + start)) 
                : rev(w, '')
  )
);
//reverse("olleh"): "hello"

または順序木探索:

const inorder = rec(go => ((node, visit) => !!(node && [go(node.left, visit), visit(node), go(node.right, visit)])));

//inorder({left:{value:3},value:4,right:{value:5}}, function(n) {console.log(n.value)})
// calls console.log(3)
// calls console.log(4)
// calls console.log(5)
// returns true
4
Bill Barry

TL; DR:

const rec = f => f((...xs) => rec(f)(...xs));

proper Y -のバリエーションには多くの答えがありますが、それは少し冗長です... Yを説明する通常の方法は「再帰がない場合はどうなるか」なので、Y自分自身を参照することはできません。しかし、ここでの目標は実用的なコンビネータであるため、それを行う理由はありません。自分自身を使用してrecを定義する この答え がありますが、カレーではなく引数を追加するため、複雑で見苦しいものです。

単純に再帰的に定義されたYは、

const rec = f => f(rec(f));

jSはレイジーではないので、上記は必要なラッピングを追加します。

4
Eli Barzilay
var rec = () => {rec()};
rec();

それはオプションでしょうか?

3
philipp

厳密モードでは非推奨/機能しないため、_arguments.callee_は悪いオプションであり、var func = () => {}のようなことも悪いので、この回答で説明されているようなハックがおそらく唯一のオプションです。

javascript:再帰的な匿名関数?

3
Jonathan Lerner

私は提供されたソリューションが本当に複雑で、正直にそれらのどれも理解できなかったので、もっと簡単なソリューションを自分で考えました(私はそれがすでに知られていると確信していますが、ここに私の思考プロセスがあります):

だから階乗関数を作っている

_x => x < 2 ? x : x * (???)
_

(???)は関数がそれ自体を呼び出すことになっていますが、名前を付けることができないため、明らかな解決策はそれを引数としてそれ自体に渡すことです

_f => x => x < 2 ? x : x * f(x-1)
_

これはうまくいきません。 f(x-1)を呼び出すときは、この関数自体を呼び出しており、引数を1)fとして定義しただけです。もう一度、関数自体と2)x値。関数自体はありますが、f覚えていますか?最初に渡してください:

_f => x => x < 2 ? x : x * f(f)(x-1)
                            ^ the new bit
_

以上です。自分自身を最初の引数として取る関数を作成し、階乗関数を生成しました!文字通りそれ自体に渡します:

_(f => x => x < 2 ? x : x * f(f)(x-1))(f => x => x < 2 ? x : x * f(f)(x-1))(5)
>120
_

それを2回書く代わりに、それ自体に引数を渡す別の関数を作成できます。

_y => y(y)
_

そして、階乗作成関数をそれに渡します:

_(y => y(y))(f => x => x < 2 ? x : x * f(f)(x-1))(5)
>120
_

ブーム。ここに小さな公式があります:

_(y => y(y))(f => x => endCondition(x) ? default(x) : operation(x)(f(f)(NeXTSTEP(x))))
_

0からxに数値を追加する基本的な関数の場合、endConditionは繰り返しを停止する必要があるため、_x => x == 0_です。 defaultは、endConditionが満たされたときに最後に与える値なので、_x => x_です。 operationは、Factorialでの乗算やフィボナッチでの加算など、再帰ごとに実行している単純な演算です:_x1 => x2 => x1 + x2_。最後に、NeXTSTEPは関数に渡す次の値で、通常は現在の値から1を引いたものです:_x => x - 1_。適用:

_(y => y(y))(f => x => x == 0 ? x : x + f(f)(x - 1))(5)
>15
_
2
H. Saleh

これはこの回答のバージョン https://stackoverflow.com/a/3903334/68922 で、矢印関数が付いています。

UまたはYコンビネーターを使用できます。 Y Combinatorは最も簡単に使用できます。

Uコンビネータ。これを使用して、関数を渡し続ける必要があります:const U = f => f(f) U(selfFn => arg => selfFn(selfFn)('to infinity and beyond'))

Yコンビネータ。これを使用すると、関数を渡し続ける必要はありません:const Y = gen => U(f => gen((...args) => f(f)(...args))) Y(selfFn => arg => selfFn('to infinity and beyond'))

2
Ricardo Freitas

関数をiife内の変数に割り当てることができます

var countdown = f=>(f=a=>{
  console.log(a)
  if(a>0) f(--a)
})()

countdown(3)

//3
//2
//1
//0
0
william malo

最も簡単な解決策は、あなたが持っていない唯一のもの、つまり関数自体への参照を見ることだと思います。あなたがそれを持っているなら、そのときの反省は取るに足らないものだからです。

驚くべきことに、それは高次関数によって可能です。

let generateTheNeededValue = (f, ...args) => f(f, ...args);

この関数は、名前がsugestsであるため、必要な参照を生成します。これを関数に適用するだけです

(generateTheNeededValue)(ourFunction, ourFunctionArgs)

しかし、これを使用することの問題は、関数定義が非常に特別な最初の引数を期待する必要があることです

let ourFunction = (me, ...ourArgs) => {...}

この特別な値を「私」と呼んでいます。そして今、私たちは再帰が必要なときはいつもこのようにします

me(me, ...argsOnRecursion);

そのすべてで。これで、単純な階乗関数を作成できます。

((f, ...args) => f(f, ...args))((me, x) => {
  if(x < 2) {
    return 1;
  } else {
    return x * me(me, x - 1);
  }
}, 4)

-> 24

私もこれの一つのライナーを見てみたいです

((f, ...args) => f(f, ...args))((me, x) => (x < 2) ? 1 : (x * me(me, x - 1)), 4)
0
gabriel80546