高級関数の技巧-関数のカリー化

6367 ワード

私たちはよくJavascript言語の中で、関数は「一等公民」と言いますが、それらは本質的に非常に簡単で、プロセス化されています.関数を利用して、いくつかの簡単なデータ処理を行い、return結果、またはいくつかの追加的な機能があり、最後にしばしばreturnの匿名関数を使用することによって実現される必要がある.
関数式プログラミングに対して一定の理解があれば、関数コリック化は不可欠であり、関数コリック化を利用して、開発の中で非常に優雅に複雑な論理を処理することができます.
関数コリック化
カリー化(Currying)とウィキペディアでは、複数のパラメータを受け入れる関数を単一のパラメータを受け入れる関数に変換するという簡単な例を先に見てください.
    //    
    var foo = function(x) {
        return function(y) {
            return x + y
        }
    }
    
    foo(3)(4)       // 7

    
    //     
    var add = function(x, y) {
        return x + y;
    }
    
    add(3, 4)       //7 
本来は一度に二つのパラメータのadd関数を導入すべきであったが、コーリー化方法は呼び出し毎に一つのパラメータだけが導入され、二回呼び出した後、最後の結果が得られるようになった.
もう一度見てみてください.定番の面接問題です.
    sum  ,      :
 console.log(sum(1)(2)(3)) // 6.
直接カバーには上のコリメート関数を追加してください.returnを追加してください.
   function sum(a) {
        return function(b) {
            return function(c) {
                return a + b + c;
            }
        }
    }
もちろん、コリー化は面接問題を解決するためではなく、関数的にプログラミングされたものです.
どうやって実現しますか
やはり上の経典の面接試験問題を見てみます.sum(1)(2)(3)(4)(5)...(n)を実現するためには、入れ子n-1の匿名関数が必要であり、
   function sum(a) {
        return function(b) {
             ...
            return function(n) {
                
            }
        }
    }
    
優雅ではないように見えますが、どれぐらいのパラメータが入ってくるかを予め知っていれば、再帰的な方法で解決できます.
    var add = function(num1, num2) {
        return num1 + num2;
    }
    
    //    sum      ,           
    function curry(add, n) {
       var count = 0,
           arr = [];
           
       return function reply(arg) {
           arr.push(arg);
           
           if ( ++count >= n) {
               //            ,         
               return arr.reduce(function(p, c) {
                   return p = add(p, c);
               }, 0) 
           } else {
               return reply;
           }
       }
    }
    var sum = curry(add, 4);
    
    sum(4)(3)(2)(1)  // 10
もし呼び出し回数が約束の数を超えたら、sumはエラーを出すことができます.このように設計できます.
sum(1)(2)(3)(4)(); //        ,      ,
簡単な修正が必要なだけでcurry関数があります.
function curry(add) {
       var arr = [];
       
       return function reply() {
         var arg = Array.prototype.slice.call(arguments);
         arr = arr.concat(arg);
         
          if (arg.length === 0) { //       ,         
              return arr.reduce(function(p, c) {
                  return p = add(p, c);
              }, 0)
          } else {
              return reply;
          }
      }
    }
  
  console.log(sum(4)(3)(2)(1)(5)())   // 15
簡潔版実現
上記は具体的な問題に対して、カリー化方法を導入して解答して、カリー化関数を作るための一般的な方法をどうやって実現しますか?同じ簡単なバージョンの方法を先に見て、add方法を例にして、コードは「JavaScript高級プログラム設計」から来ます.
 function curry(fn) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return fn.apply(null, finalArgs);
    };
}

function add(num1, num2) {
    return num1 + num2;
}
var curriedAdd = curry(add, 5);

var curriedAdd2 = curry(add, 5, 12);

alert(curriedAdd(3))    // 8
alert(curriedAdd2())    // 17
強化版の実現
上のadd関数は、他の関数に変えられます.curry関数によって処理されても、コリメート関数に変換できます.ここでcurry初期化が呼び出されると、一つのパラメータが導入され、また戻ってきた関数curriedAddcurriedAdd2もコリック化されていない.より一般的な方法を実現するには、カリー関数が本格的に呼び出された時に、パラメータを転送します.
function curry(fn) {
     ...
 }

function add(num1, num2) {
    return num1 + num2;
}

var curriedAdd = curry(add);

curriedAdd(3)(4) // 7
curryから戻ってきた関数をコールするたびに、コリック化され、1つまたは複数のパラメータが呼び出され続けます.
上のsum(1)(2)(3)(4)と非常に似ています.再帰的に利用すれば実現できます.鍵は再帰的な輸出です.ここでは一つの空のパラメータの呼び出しではなく、元の関数が定義されている時、パラメータの総個数、カリー関数が呼び出された時、元の関数の総個数を満たしたら、計算結果に戻ります.そうでなければ、引き続きカリー化関数に戻ります.
元関数のエントリ総数は、length属性を利用して得られます.
function add(num1, num2) {
    return num1 + num2;
}

add.length // 2
上のコードを結合して、
    var curry = function(f) {
      var len = f.length;
      
        return function t() {
          var innerLength = arguments.length,
            args = Array.prototype.slice.call(arguments);
            
          if (innerLength >= len) {   //     ,f.length
             return f.apply(undefined, args)
          } else {
            return function() {
              var innerArgs = Array.prototype.slice.call(arguments),
                allArgs = args.concat(innerArgs);
                
              return t.apply(undefined, allArgs)
            }
          }
        }
    }
    
   //     
  function add(num1, num2) {
    return num1 + num2;
  }

   var curriedAdd = curry(add);
   add(2)(3);     //5

  //     
  function identity(value) {
     return value;
 }

   var curriedIdentify = curry(identify);
   curriedIdentify(4) // 4
ここで、カリー化の汎用関数はほとんどの需要を満たすことができます.
applyを使って再帰的に呼び出した場合、デフォルトはundefinedに入ってきます.他のシーンでは、contextに入ってきて、指定環境をバインドする必要があるかもしれません.
実際の開発、lodash.curryを使うことを推薦して、具体的に実現して、curryソースコードを参考にすることができます.
使用シーン
このように多くのcurry関数の異なる実装方法を説明しましたが、一般的な方法が実現された後、それらのシーンで使用できます.あるいは、コリメート関数を使って実際にコード品質を向上させることができますか?
  • パラメータ多重化は、「JavaScript高級プログラム設計」の簡単版のcurry関数
      var curriedAdd = curry(add, 5)
    の後に、curriedAdd関数を使用すると、デフォルトは5を多重化しています.2つのパラメータ
  • に再入力する必要はありません.
  • は、上述した複数のパラメータの実行を遅延させるsum(1)(2)(3)であり、遅延実行の最後の例であり、着信パラメータの個数は、元の関数のパラメータの個数を満たしていないので、結果をすぐに返さない.同様のシーン、さらにバインディングイベントのフィードバックがあり、bind()方法を使用してコンテキストをバインドし、着信パラメータは同様であり、
       addEventListener('click', hander.bind(this, arg1,arg2...))
       
       addEventListener('click', curry(hander)) 
       
    の遅延実行の特性は、実行関数の外で、匿名関数の層を包むことを避けることができ、curry関数はコールバック関数として大きな利点がある.
  • 関数式プログラミングの中で、compose、functor、mondなどの実現の基礎として、カリー化は関数式プログラミングによって生まれたと言われています.中に現れる確率はとても大きいです.JS関数式プログラミングガイドの中で、カーリー化の重要性を紹介しました.
  • 追加オーバーヘッドについて
    関数コリック化は複雑なアルゴリズムと機能を構築するために使用できるが、悪用は追加のオーバーヘッドをもたらす.
    上の実装部分のコードからは、コリック関数を使って、クローズド、アーグメンツから再帰することができます.
    クローズド、関数内の変数はすべてメモリに保存されています.メモリの消耗が大きく、メモリの漏洩を引き起こす可能性があります.再帰的には、効率が非常に悪く、argments、変数のアクセスが遅く、アクセス性が悪いです.
    参照リンク
  • JS関数式プログラミングガイド
  • 出会い関数カリン化
  • JavaScript関数のコリック化を把握する