機能JavaScriptの再帰

Joseph Gordon-Levitt
リリース: 2025-02-19 10:22:09
オリジナル
338 人が閲覧しました

Recursion in Functional JavaScript

JavaScriptの再帰関数を聞いたことがあるかもしれませんし、いくつかを書き込もうとしました。しかし、実際に機能する再帰の多くの例を見たことがないかもしれません。実際、このアプローチの特殊性に加えて、再帰がいつどこで役立つか、または不適切に使用した場合、それがどれほど危険であるかを考慮していないかもしれません。

キーポイント

  • 再帰は、結果に到達するまで関数が繰り返し呼び出すことを可能にするJavaScriptメソッドです。これは、フラクタル数学、並べ替え、複雑なデータ構造や非線形データ構造などの反復枝を含む問題に特に役立ちます。
  • 再帰は、コードをより簡潔で理解しやすくすることができますが、不適切に使用すると、エンジンのメモリ容量を超えるリスクがあるために危険になる可能性があります。これは、JavaScriptの再帰関数が、適切な場所で実行され続けることができるように、毎回それらが呼び出される場所を追跡する必要があるためです。
  • 多くの機能的なプログラミング言語では、再帰を管理するためにテールコール最適化と呼ばれる手法が使用されます。これにより、メモリに積み上げられるのではなく、再帰関数内の各連続ループがすぐに発生することができます。ただし、ほとんどのJavaScriptコンパイラはまだ最適化されていません。
  • カスタムバウンス関数は、再帰的な実行を繰り返し管理するために構築でき、一度にスタックに1つの操作のみを残すことができます。これは、実行されるのを待っている深いスタック操作の作成を避けるのに役立ちますが、通常はパフォーマンスと読みやすさを犠牲にしています。

再帰の目的再帰は、結果が得られるまで繰り返しそれ自体を呼び出すことにより、操作を繰り返す手法です。ほとんどのループは再帰スタイルで書き直すことができ、一部の機能的なプログラミング言語では、このループ方法がデフォルトです。

ただし、JavaScriptの機能プログラミングスタイルは再帰機能をサポートしていますが、ほとんどのJavaScriptコンパイラが現在安全に最適化されていないことを認識する必要があります。

ループ内の異なるパラメーターを使用して同じ関数を繰り返し呼び出す必要がある場合に、再帰を使用するのが最適です。多くの場合に使用できますが、フラクタル数学、複雑なデータ構造または非線形データ構造のノードの並べ替えまたは通過などの反復枝を含む問題を解決するのに最も効果的です。

機能的なプログラミング言語で再帰が好まれる理由の1つは、状態を設定および維持するためにローカル変数を使用する必要がない構築コードを許可することです。再帰関数は、純粋な方法で簡単に記述し、特定の入力に対して特定の一貫した返品値を持ち、外部変数の状態に副作用がないため、テストも簡単です。

サイクル

再帰を適用できる古典的な関数の例は要因です。これは、以前の各整数を繰り返し乗算し、1までの数の結果を返す関数です。 たとえば、

3の要因は次のとおりです

6の要因は次のとおりです
<code>3 × 2 × 1 = 6</code>
ログイン後にコピー

これらの結果がどれほど速く大きくなるかを見ることができます。また、私たちが同じ動作を何度も繰り返しているのを見ることができます。乗算操作の結果を取得し、2番目の値でマイナス1に乗算します。その後、1に達するまで何度も何度もこれを行います。

for loopを使用すると、正しい結果が返されるまでこれを行うために反復する関数を作成することは難しくありません:

<code>6 × 5 × 4 × 3 × 2 × 1 = 720</code>
ログイン後にコピー

これは機能しますが、機能的なプログラミングの観点からは、エレガントではありません。 forループをサポートしてから結果を返すには、状態を維持および追跡するいくつかのローカル変数を使用する必要があります。 forループを破棄し、より機能的なJavaScriptメソッドを採用することができれば、それはもっと簡潔ではないでしょうか?

再帰

JavaScriptを使用すると、関数をパラメーターとして使用する関数を書き込むことができることがわかっています。では、実際の関数を使用したい場合は、それを実行して実行するコンテキストで実行したい場合はどうなりますか?

これは可能ですか?もちろん!たとえば、このようなシンプルなwhile loop:

を考慮してください
var factor = function(number) {
  var result = 1;
  var count;
  for (count = number; count > 1; count--) {
    result *= count;
  }
  return result;
};
console.log(factor(6));
// 720
ログイン後にコピー

これが完了した後、カウンターの値は変更されましたが、ループは各値を印刷する仕事を完了しました。

同じループの再帰バージョンは次のようになる場合があります:

var counter = 10;
while(counter > 0) {
    console.log(counter--);
}
ログイン後にコピー

カウントダウン関数の定義でカウントダウン関数を直接呼ぶ方法を見ましたか? JavaScriptはそれをボスのように処理し、あなたが望んでいることだけをします。 CountDownが実行されるたびに、JavaScriptはそれが呼び出される場所を追跡し、その関数呼び出しのスタックに戻り、完了するまで戻ります。また、私たちの機能は、変数の状態を変更することも避けますが、再回帰を制御するために合格した値を使用します。

要因のケースに戻ると、このような以前の関数を書き換えて再帰を使用できます。

この方法でコードを書くことで、副作用なしにプロセス全体をステートレスの方法で説明できます。また、最初に関数に渡されたパラメーターの値を最初にテストし、次に計算を実行することも注目に値します。終了に達したときに迅速かつきれいに出るように自分自身を呼び出そうとしている関数が必要です。この方法で計算された要因の場合、着信数がゼロまたは負の場合、終了状況に達します(必要に応じてネガティブ値をテストし、異なるメッセージを返すこともできます)。
var countdown = function(value) {
    if (value > 0) {
        console.log(value);
        return countdown(value - 1);
    } else {
        return value;
    }
};
countdown(10);
ログイン後にコピー

テールコール最適化

現代のJavaScriptの実装の問題の1つは、再帰機能が無限に積み重ねられ、エンジンの容量を超えるまでメモリを消費するのを防ぐための標準的な方法がないことです。 JavaScriptの再帰関数は、適切な場所で実行を続けることができるように、毎回それらが呼び出されている場所を追跡する必要があります。

HaskellやSchemeなどの多くの機能的なプログラミング言語では、Tail Call Optimizationと呼ばれる手法を使用して管理されています。テールコールの最適化を使用すると、再帰関数の各連続ループは、メモリに積み上げられるのではなく、すぐに発生します。

理論的には、テールコールオプティメーションはECMAScript 6(現在のJavaScriptの次のバージョン)標準の一部ですが、ほとんどのプラットフォームはまだ完全に実装していません。

バウンス関数

必要に応じて、JavaScriptに安全な方法で再帰関数を実行するように強制する方法があります。たとえば、カスタムバウンス関数は、再帰的な実行を繰り返し管理するために構築でき、一度にスタックに1つの操作のみを残すことができます。この方法で使用されるバウンス関数は、再帰関数をそれ自体に戻すために、特定のコンテキストに関数をバインドするJavaScriptの能力を利用して、ループが完了するまで一度に結果を構築します。これにより、実行を待つディープスタック操作の作成が避けられます。

実際、バウンス関数を使用すると、多くの場合、安全性のパフォーマンスが低下します。さらに、このアプローチをJavaScriptで機能させるために必要なコードの畳み込みで、再帰的に機能を書くことで得られる優雅さと読みやすさのほとんどが失われます。

あなたが興味があるなら、この概念についてもっと読んで、以下の議論であなたの考えを共有することをお勧めします。 Stackoverflowの短いトピックから始めて、JavaScriptのバウンス機能の長所と短所に深く入るDon TaylorとMark McDonnellの記事を探索できます。

その時点ではまだ再帰は、知る価値のある強力なテクニックです。多くの場合、再帰は複雑な問題を解決するための最も簡単な方法です。ただし、ECMAScript 6が必要な場所でテールコールの最適化を完全に実装する前に、再帰がどのようにどのように適用されるかについて非常に注意する必要があります。

機能JavaScript(FAQS)

の再帰に関するFAQ 再帰の基本的な状況は何ですか?なぜそれが重要なのですか?

再帰の基本的な状況は、関数が無限にそれ自体を呼ぶのを防ぐ条件です。それがなければ、再帰関数は無限にそれ自体を呼び出し、スタックオーバーフローエラーを引き起こすため、それは重要です。基本的な状況は、通常、再帰呼び出しを行う前に関数がチェックする条件です。この条件が満たされた場合、関数は値を返し、自分自身を呼び出すのを止めます。

JavaScriptで再帰はどのように機能しますか?

JavaScriptでは、基本的な状況に達するまで関数自体を呼び出すことにより、再帰が機能します。関数は、基本的なケースと再帰的なケースに分割されます。基本的なケースは、関数を再度呼び出すことなく値を再度返しますが、再帰ケースは異なるパラメーターで関数を再度呼び出します。この関数は、基本ケースに到達するまで自分自身を呼び出し続け、その時点で値の返却を開始します。

JavaScriptの尾の再帰とは何ですか?

テール再帰は、特別なタイプの再帰であり、再帰コールは関数の最後の操作です。 Tail Call Optimizationという手法を使用して、JavaScriptエンジン最適化が再発できるため、これは重要です。これにより、関数が使用するメモリの量を大幅に削減し、より大きな入力を処理できるようにします。

JavaScriptで再帰を使用することの利点と短所は何ですか?

再帰は、複雑な問題をより単純な問題に壊すことで、コードをより簡潔で理解しやすくすることができます。これは、ツリーデータ構造の移動などのタスクに特に役立ちます。ただし、再帰は反復溶液よりも効率が低く、誤って実装された場合、スタックオーバーフローエラーを引き起こす可能性があります。

再帰関数のスタックオーバーフローエラーを回避する方法は?

再帰関数が何度も電話をかけすぎてコールスタックを埋めると、スタックオーバーフローエラーが発生します。これを避けるために、再帰機能が最終的に到達する基本的なケースを持っていることを確認してください。また、JavaScriptエンジンが最適化してメモリを使用するように最適化できるテール再帰の使用を検討してください。

機能プログラミングで再帰はどのように使用されていますか?

機能プログラミングでは、再帰はループの代替品としてしばしば使用されます。機能プログラミングは可変状態の使用を妨げるため、再帰を使用して、状態を変更せずに繰り返し操作を実行できます。

すべての再帰関数を反復関数に変換できますか?

はい、理論的には、すべての再帰関数を反復関数に変換できます。ただし、特に複雑なツリーまたはグラフトラバーサルを含む機能の場合、反復バージョンはより複雑で理解しにくい場合があります。

JavaScriptの相互再帰とは何ですか?

相互再帰とは、ループで互いに呼び出される2つ以上の関数を指します。これは、特定の種類の問題を解決するための強力な手法かもしれませんが、単純な再帰よりも理解してデバッグすることも難しいかもしれません。

JavaScriptで再帰関数をデバッグする方法は?

繰り返しの機能呼び出しにより、再帰関数の排出は困難な場合があります。ただし、console.logステートメントを使用して、各ステップで関数のパラメーターと戻り値を印刷すると役立つ場合があります。さらに、関数呼び出しを段階的に実行できるデバッガーツールを使用することは非常に便利です。

再帰を使用する際にパフォーマンス上の考慮事項はありますか?

はい、再帰関数は、関数呼び出しが繰り返されるため、反復的な対応物ほど効率的ではない場合があります。彼らが何度も自分自身を呼びすぎると、スタックオーバーフローエラーを引き起こす可能性もあります。ただし、多くの場合、再帰ソリューションの読みやすさとシンプルさは、これらのパフォーマンスに関する考慮事項を上回る可能性があります。

以上が機能JavaScriptの再帰の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート