Current関数-一度に機能的な一歩を行く


あなたが歴史的な文脈に興味がないならば"Let's get started" セクション.
Dr Haskell Brooks Curry 1930年代の数学者であり、論理学者であった.組合せ論とラムダ計算における彼の仕事を通して、彼の名前はプログラミング言語Haskell . 彼の姓はまた、機能を変換するためのコンピュータサイエンス技術に関連付けられている.
「好奇心旺盛な開発者のための小さなコンピューターサイエンス」の私のシリーズに合わせてより多くのlambda calculus .
数学者によって1930年代に導入されたAlonzo Church , ラムダ計算は、ラムダ式として関数を定義するための正式な(数学的)方法です.
関数パラメータは、あらかじめ設定されたギリシャ文字ラムダλで示されるラムダ式で定義され、ドット文字で区切られている.最後のドット文字の反対側は関数の式です.たとえば、X乗を実装するJS関数は、可能です.
function squared(x) {
  return x * x;
}
ラムダ式の等価物は(λx . x * x )です.これはjs関数の構文構文を連想させるかもしれません.
const squared = x => x * x;  // or x**2;
ラムダ式とJS関数の1つの重要な違いは、入力が動作する方法です.通常の関数(および矢印の関数)では、呼び出されたときにすべての引数を(パラメータにバインド)されなければなりません.ラムダ式を使用すると、すべての入力が一度に、または順序でさえバインドされます.
Curryingはさらに1つのステップに行きます.
λx (λy x y y )
これは矢印関数と等価です.
const curriedProduct = x => y => x * y;

const times6 = curriedProduct(6);

console.log(times6(7)); // 42

始めましょう


「一連の機能的ワンステップ」におけるこのシリーズの前のポストでは、様々な方法で実装されたFP概念を調査した.
注意:いくつかのFPの純粋主義者はおそらく、このポストのすべてのではない場合、いくつかの意見に反します.このポストは、私が役に立つとわかる方法で話題(私の開発者として)の私の理解について説明します、そして、うまくいけば、あなたはそうします.
これらは別のFPの概念は“時折”は、時折部分的なアプリケーションと混乱するように見えると呼ばれるです.関係している間、私の心では、彼らは異なった概念です.
部分的なアプリケーションは、コードを呼び出して、各呼び出しでより多くの引数を提供するいくつかの回関数を呼び出すことができます.一旦関数のすべてのパラメタが供給されるならば、機能は実行されます.
Curryingは、複数の引数を一度に一度に実行し、すぐに実行される関数を変換するプロセス(少なくともJavaScript)で、引数を一度に1つずつ引数をバインドすることを期待する関数にします.以下を含むいくつかの実装は、各呼び出しのパラメータに複数の引数をバインドすることができます.この関数は全ての必須パラメータが引数にバインドされた後に実行されます.

四段階部分申請


例として、オブジェクトの配列をフィルター処理するシナリオを使用して、検索用語にマッチするオブジェクトをいくつかの方法で展開します.実行する関数は、オブジェクト(配列から)を取り、Boolean値を返す述語です.これにより、filter 対応するオブジェクトを選択する配列のメソッドです.
The filterBySearchTerm 関数は4つの引数を必要とする.
  • まず、検索用語をObjectプロパティと比較するための関数を提供します.
  • 次に、一致するプロパティの名前を識別します.
  • その後、直前に検索用語を供給する
  • 最後に、配列から各項目をフィルター操作内の関数に渡します.
  • テストデータ


    ここでは、作業関数を示すために使用するデータの配列を示します.
    const testData = [
      {name: 'Alice', age: 31},
      {name: 'Bob', age: 32},
      {name: 'Charlie', age: 33},
      {name: 'David', age: 34},
      {name: 'Eve', age: 35},
      {name: 'Fred', age: 36}
    ];
    console.table(testData);
    
    /*
    ┌─────────┬───────────┬─────┐
    │ (index) │   name    │ age │
    ├─────────┼───────────┼─────┤
    │    0    │  'Alice'  │ 31  │
    │    1    │   'Bob'   │ 32  │
    │    2    │ 'Charlie' │ 33  │
    │    3    │  'David'  │ 34  │
    │    4    │   'Eve'   │ 35  │
    │    5    │  'Fred'   │ 36  │
    └─────────┴───────────┴─────┘
    */
    

    実行と期待結果


    物語がどのように終わるかを見るために、スキップしましょう.
    const nameContains = filterContains('name'); // prop
    const nameContainsTheSearchTerm = nameContains('e');
    
    const results = testData.filter(nameContainsTheSearchTerm);
    console.table(results);
    
    /*
    ┌─────────┬───────────┬─────┐
    │ (index) │   name    │ age │
    ├─────────┼───────────┼─────┤
    │    0    │  'Alice'  │ 31  │
    │    1    │ 'Charlie' │ 33  │
    │    2    │   'Eve'   │ 35  │
    │    3    │  'Fred'   │ 36  │
    └─────────┴───────────┴─────┘
    */
    
    検索用語は1文字を含む文字列で、述語生成関数はnameContains この例では.
    我々は、同じキュリー関数を使用しますfilterConstuctor 検索用語を使用する次の例を実行するにはsearchAge は数値であり、述語ジェネレータはfilterGreaterThanAge32 .
    const searchAge = 32;
    const filterGreaterThanAge = filterGreaterThan('age');
    const filterGreaterThanAge32 = filterGreaterThanAge(searchAge);
    
    const results = testData.filter(filterGreaterThanAge32);
    console.table(results);
    
    /*
    ┌─────────┬───────────┬─────┐
    │ (index) │   name    │ age │
    ├─────────┼───────────┼─────┤
    │    0    │ 'Charlie' │ 33  │
    │    1    │  'David'  │ 34  │
    │    2    │   'Eve'   │ 35  │
    │    3    │  'Fred'   │ 36  │
    └─────────┴───────────┴─────┘
    */
    
    では、どのように使用し、どのように書くことができますかfilterConstuctor 関数を生成するnameContainsTheSearchTerm and filterGreaterThanAge32 述語ジェネレータ?

    フィルタ定数の使用


    述語生成器は、まず以下のように比較関数を供給することによって構成される.
    const filterContains = filterConstuctor(
      (prop, searchTerm) => prop.includes(searchTerm)
    );
    
    // and
    
    const filterGreaterThan = filterConstuctor(
      (prop, searchAge) => prop > searchAge
    );
    
    これらの関数は、比較対象のオブジェクト内のプロパティ名を指定するために呼び出されます.
    const nameContains = filterContains('name'); // prop
    
    // and
    
    const filterGreaterThanAge = filterGreaterThan('age'); // prop
    
    我々は間接的に(無料のスタイルで)これらの機能を使用することができますまたは直接.どちらも同じように、よく選択された仕事(私は私がしないと言うように)の意図は、明らかにいずれかの方法を明らかにすることができます.
    // Indirectly
    const nameContainsTheSearchTerm = nameContains('e');
    
    const results = testData.filter(nameContainsTheSearchTerm);
    
    // Directly
    const results = testData.filter(greaterThanAge(32));
    

    FilterConstuctor関数の作成


    少なくとも2つの方法があります.この関数を書くことができます.我々は両方の両方を探索する方法をよりよく理解するために動作します.
    フィルタコンストラクタの形式
    長い特定の形
    function filterConstuctor(compareFn) {
      return function getProperty(prop) {
         return function getSearchTerm(searchTerm) {
           return (item) => compareFn(item[prop], searchTerm);
        }
      }
    }
    
    矢印関数を使用すると、実際にそれを読みやすくすることができます.
    短い特定の形
    function filterConstuctor(compareFn) {
      return (prop) => (searchTerm) => (item) => 
        compareFn(item[prop], searchTerm);
    }
    
    短いジェネリック形式
    const filterConstuctor = curry(uncurriedFilterPredicate);
    
    function uncurriedFilterConstuctor(compareFn, prop, searchTerm) { 
      return item => compareFn(item[prop], searchTerm);
    }
    
    このポストのcruxはcurry 関数.もちろん、あなたはこれを行うことはありませんが、どのようにするかを知るのに便利です.代わりに、この種のもののために試みられてテストされた機能を提供するライブラリの1つを使用するのは賢明ですlodash . ロダッシュを見てくださいpage on the curry function .

    The curry 機能
    次のカレー関数の実装では、コードを簡潔に保つクロージャ内で再帰的手法を使用します.各サイクルは、与えられた引数(s)を配列に加えます.十分な引数が与えられた場合、拡張された配列を使用して元の関数が呼び出されます.
    簡単な一般的な機能
    function curry(fnUncurried) {
      const expectedParameters = fnUncurried.length;
      const actualArguments = [];
      return curriedFunction;
    
      function curriedFunction(arg) {
        actualArguments.push(arg);
        return (actualArguments.length === expectedParameters) ?
          fnUncurried(...actualArguments) : curriedFunction;
      } 
    }
    
    用心深い言葉
  • オプションのパラメータは、カウントされませんFunction.length したがって、関数内で管理する必要があります.
  • 上記の実装は一度に1つの引数を受け入れるだけです.この制限は、以下のバージョン(MK 3)では、アレイの残りの部分とスプレッドの操作を使用して克服されています.
  • の実装curry 上記のように、curly関数を再利用する前に毎回実行する必要があります.次のバージョン(MK 4)では、この制限に対処します.
  • 多引数汎用関数
    function curry(fnUncurried) {
      const actualArguments = [];
      return curriedFunction;
    
      function curriedFunction(...args) {
        actualArguments.push(...args);
        return actualArguments.length === fnUncurried.length
          ? fnUncurried(...actualArguments)
          : curriedFunction;
      }
    }
    
    MK 4 -再利用可能な一般的な機能
    function curry(fnUncurried) {
      const actualArguments = [];
      return curriedFunction;
    
      function curriedFunction(...args) {
        actualArguments.push(...args);
        return actualArguments.length === fnUncurried.length
          ? runFunction()
          : curriedFunction;
      }
      function runFunction() {
        const retVal = fnUncurried(...actualArguments);
        actualArguments.length = 0;
        return retVal;
      }
    }
    
    の上のlodashページに示される例でcurry メソッドは、生成された関数が1つずつ引数を取ることを強制されていないことに気づいているかもしれませんが、バッチで、一度に、そしてシーケンスからでさえ供給することができます.実際、私は長いフォームの例(MK 2)のように引数を一つずつ受け入れることを強制するような、キュリーされた関数の必要性を考えます.
    それでは、さらに一歩して、各呼び出しで引数の変数(無制限)数の条項をサポートしよう.我々は、支持する限り
    議論の順序外.

    仕上げる


    呼び出しが行われなくなるまで引数を受け取るカレー関数を作成することができます.この関数は、そのポイントに与えられたすべての引数を使用して呼び出されます.私はそれのために特定のユースケースをすることができません、しかし、それは楽しいアカデミックな運動であると思います.
    無制限のArgsジェネリック関数
    function curry(fnUncurried) {
      const actualArguments = [];
      return curriedFunction;
    
      function curriedFunction(...args) {
        return args.length
          ? captureArgs(args)
          : runFunction();
      }
      function captureArgs(args) {
        actualArguments.push(...args);
        return curriedFunction;
      }
      function runFunction() {
        const retVal = fnUncurried(...actualArguments);
        actualArguments.length = 0;
        return retVal;
      }
    }
    
    このようなカレー関数を使用すると、以下に示すように、カレー関数を呼び出す方法が異なります.
    const results = testData.filter(nameContains('e')());
    
    // and
    
    const filterGreaterThanAge32 = filterGreaterThan('age', 32);
    
    const results = testData.filter(filterGreaterThanAge32());
    

    結論


    部分的なアプリケーションは、関数が呼ばれるたびに供給される必要のある引数の数を減らすための非常に便利なテクニックです.コールバック関数が通常必要とするパラメータに加えて、イベントハンドラ、ソート比較やマップ変換などのコールバック関数を提供する場合に特に便利です.
    CurryingはHaskellのような多くの関数プログラミング言語に組み込まれていますが、追加処理やJavaScriptのライブラリが必要です.JSのそのユーティリティは限られているが、プロセスを理解し、機能を作成するために使用されるメカニズムは、貴重な学習演習です.
    この投稿のサポートコードはJSFiddle .