もう一度カリー化とPHPでの部分的なアプリケーションについて

 最近の記事では、PHPでのカリー化と部分関数アプリケーションの実装が提案されました。 その根本的な欠点は、カリー化が機能ではなくオブジェクトであることです。 コールバックパラメーターとして渡すことはできなくなり、引数を置き換える特別な構文を使用する必要があります。 このテキストでは、PHP 5.3以降のこれらの構造の新しい透過的な実装を提案しています。



カレーという用語 は、アメリカの数学者Haskell Curryの名前に由来しています。 カリーという言葉の2番目の意味は、 なめした革のドレッシングです。



カリー化と部分的なアプリケーションの概念は、関数型プログラミング言語に由来し、その中で最も幅広いアプリケーションが見つかります。 現代のPHPは、関数型プログラミングの一部の要素(ファーストクラスのオブジェクト、匿名関数、クロージャーとしての関数)を借用する傾向があるため、議論された概念はもはや完全に異質ではありません。



カリー化とPHPでの部分的な使用のエミュレーションは、McConnellが言語ではなく言語を使用してパーフェクトプログラミング(4.3章)プログラミングと呼ぶものの一例です。



簡単な教育プログラム



カリー化と部分的なアプリケーションは、機能ファクトリを構築するために使用されます。 この手法は、ユーザーフィルタリング、並べ替え、変換などを実行するための引数として別の関数に渡すために、指定されたインターフェイスを持つ関数を生成する必要がある場合に特に役立ちます。特定の引数を修正するときに与えられます。



たとえば、「ブラックボックス」-solve( fx 0ε )関数があるとします。この関数は、初期点x 0の近傍でεの精度で方程式fx )= 0の解を求めます。 次に、部分的なアプリケーション呼び出しを使用して、関数solve1( x 0ε )≡solve( x -tg xx 0ε )を作成できます。 あるいは、関数solve2( ε )でさえ、可変の精度で固定された開始点の近傍にある一定の固定された方程式を解きます。



もちろん、特定のケースごとに、次の型のラッパー関数を作成できます。

function solve_x_minus_tan_x($x0, $eps){ $f = function($x) { return $x - tan($x); }; return solve($f, $x0, $eps); }
      
      





ただし、部分的なアプリケーションであるユニバーサルメカニズムを使用する方が便利です。



カリー化は、 n個の変数の関数を1つの変数のn個の関数のチェーンに変換し、代替引数を実行するプロシージャです。 たとえば、add( ab )= a + b 、およびcurry_addをadd関数のカリー化の結果とします。 次に、各aに対するcurry_add( a )の呼び出しは、1つの引数の関数を生成し、それにaを追加します。つまり、curry_add( a )( b )= add( ab )です。 その他の例を以下に示します。



カリー化と部分適用の詳細については、E。Kirpichevの大きな記事「 関数型言語の要素 」(セクション5)を参照してください。



カレー機能



だから、スライド。 必要なのは、元の関数をカリー化したバージョンに置き換える次のコードだけです。

 function curry($callback, $args = array()){ /* $callback -   $args -   ,     */ /*    */ $ret = function() use($callback, $args){ /*      */ $func = new ReflectionFunction($callback); $num = $func->getNumberOfParameters(); /*        */ $args = array_merge($args, func_get_args()); /*      , */ if(count($args) >= $num){ /*           */ return call_user_func_array($callback, $args); } /*    ,  , */ else { /*            */ return curry($callback, $args); } }; return $ret; }
      
      





古典的な例



add関数を定義してみましょう。

 function add($a, $b) { return $a + $b; }
      
      





カレー版を作成できます。

 $add = curry("add");
      
      





取得した関数が元の関数と同じように動作することを確認します。

 echo $add(2, 5); //  7
      
      





ここで、増分関数と減分関数を生成するために最初の引数のみを置き換えます。

 $inc = $add(1); $dec = $add(-1); echo $inc(6); //  7 echo $dec(8); //  7
      
      





その他の例



任意の数の初期引数を完全に透過的に置換し、すべての引数を再び置換できるわけではない本格的な関数を取得できます。 例えば

 function add_and_mul($a, $b, $c) { return $a + $b * $c; } $add_and_mul = curry("add_and_mul"); $test1 = $add_and_mul(1, 2, 3); //    $test2 = $add_and_mul(1, 2); //    $test3 = $add_and_mul(1); //    $test4 = $test3(2); //    //     7 echo $test1; echo $test2(3); echo $test3(2, 3); echo $test4(3);
      
      





カリー化の結果は、コールバックパラメーターとして別の関数に簡単に渡すことができます。 たとえば、立方体の質量を密度と辺の長さの配列で計算する必要があるとします。 次のようにこれを行うことができます。

 /* ,         */ function mass($density, $length){ return $density * $length * $length * $length; } /*  */ $mass = curry("mass"); /* ,     */ $steel_mass = $mass(7.9); /*     */ $lengths = array(3, 2, 5, 6, 1); /*    */ $masses = array_map($steel_mass, $lengths); /*  Array ( [0] => 213.3 [1] => 63.2 [2] => 987.5 [3] => 1706.4 [4] => 7.9 ) */ print_r($masses);
      
      





注釈



  1. PHPインタープリターの観点からは、カリー化の結果は関数ではなく、 Closureクラスのオブジェクトです。これは、匿名クロージャーとして構築されているためです。 ただし、構文に関しては、置換は完全に透過的です。
  2. 明らかな理由により、printf()型の可変数の引数を持つ関数をカリー化することはできません。 実装では、元の署名でオプションとしてマークされている場合でも、すべての関数引数は必須になります。 また、カリー化された関数の引数の数をカウントしようとすると、getNumberOfParameters()は0を返すことに注意してください。
  3. 厳密に言えば、カリー化された関数は一度に1つの引数を取る必要があります。つまり、$ add(2、5)の代わりに、$ add(2)(5)を記述する必要があります。 ただし、現在のバージョンのPHPインタープリターは、タイプがfunc(arg1)(arg2)のエントリーを、意味的に正しい場合でも構文エラーと見なします。 したがって、便宜上、実装では、コンマを使用して複数の引数を一度に指定できるため、部分的なアプリケーションに近づけることができます。




更新する 08/01/12から:記事「 オブジェクト指向の機能的メタプログラミングまたはメソッドカリー化 」のPHPカリー化についても参照してください。



All Articles