📜 ⬆️ ⬇️

Once again about currying and partial application in PHP

In a recent article, the proposed implementation of currying (currying) and partial application (partial function application) in PHP. Its fundamental disadvantage is that the result of currying is not a function, but an object. It can no longer be passed as a callback parameter, and for the argument substitution one has to use a special syntax. This text proposes a new, transparent implementation of these constructs for PHP 5.3 and above.

The term currying comes from the surname of the American mathematician Haskell Curry. The second meaning of the word currying - tanning leather tanning.

The concepts of currying and partial application come from functional programming languages, within the framework of which they find the widest application. Modern PHP tends to borrow some elements of functional programming (functions as first-class objects, anonymous functions, and closures), so the concepts discussed are no longer completely foreign to it.
')
Emulation of currying and partial application in PHP is one example of what McConnell in “Perfect Code” (Ch. 4.3) calls programming using language rather than language.

Short educational program


Currying and partial application are used to build factories of functions. This technique is especially useful if you need to generate a function with a given interface to pass to another function as an argument to perform custom filtering, sorting, transformation, etc. Let us have a certain function with many parameters and we want to massively build functions that coincide with given when fixing certain arguments.

For example, suppose we have a “black box” - the function solve ( f , x 0 , ε ), which finds a solution to the equation f ( x ) = 0 in a neighborhood of the initial point x 0 with an accuracy ε . Then, by calling the partial application, we can construct the function solve1 ( x 0 , ε ) solve ( x - tan x , x 0 , ε ). Or even the solve2 ( ε ) function, which would solve some fixed equation in a neighborhood of a fixed initial point with variable accuracy.

Of course, in each particular case we can write a wrapper function of type
function solve_x_minus_tan_x($x0, $eps){ $f = function($x) { return $x - tan($x); }; return solve($f, $x0, $eps); } 

however, it would be more convenient to have a universal mechanism, which is a partial application.

Currying is a procedure that converts a function of n variables into a chain of n functions of one variable, performing alternate argument substitution. For example, let add ( a , b ) = a + b , a curry_add be the result of currying the add function. Then the call to curry_add ( a ) for each a will generate functions of one argument, adding a to it, that is, curry_add ( a ) ( b ) = add ( a , b ). More examples will be given below.

More details on currying and partial application can be found in E. Kirpicheva's large article “ Elements of Functional Languages ” (section 5).

Curry function


So, slides. All we need is the following code, which replaces the original function with its curried version.
 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; } 

Classic example


Suppose we have a function add.
 function add($a, $b) { return $a + $b; } 

We can build a curried version of it.
 $add = curry("add"); 

We verify that the resulting function behaves in the same way as the original one.
 echo $add(2, 5); //  7 

Now we substitute only the first argument to generate the increment and decrement functions.
 $inc = $add(1); $dec = $add(-1); echo $inc(6); //  7 echo $dec(8); //  7 

More examples


We can completely transparently substitute an arbitrary number of initial arguments and get a full function in which not all arguments can be substituted again. For example,
 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); 

The result of currying can be passed without problems as a callback parameter of another function. For example, suppose we need to calculate the masses of cubes from the density and the array of lengths of the sides. We can do this as follows.
 /* ,         */ 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); 

Notes


  1. From the point of view of the PHP interpreter, the result of our currying is not a function, but an object of the Closure class, since it is built as an anonymous closure. However, in terms of syntax, the substitution is completely transparent.
  2. For obvious reasons, functions with a variable number of arguments of type printf () cannot be curried. In our implementation, all the arguments of the function become mandatory, even if they were marked as optional in the original signature. It should also be noted that when trying to count the number of arguments of the curved function, getNumberOfParameters () returns 0.
  3. Strictly speaking, a curried function should take arguments one by one, that is, instead of $ add (2, 5), you must write $ add (2) (5). However, the current version of the PHP interpreter considers func (arg1) (arg2) entries as a syntax error, even if semantically correct. Therefore, for convenience, our implementation allows you to specify several arguments at once, separated by commas, which brings it closer to partial application.


Unveiled from 08/01/12: See also about PHP currying into the article “ Object-Oriented Functional Meta-Programming or Currying of a Method ”.

Source: https://habr.com/ru/post/144160/


All Articles