First, let me show you how currying looks in a functional language. Here's a basic example in OCaml/F#:
let do_math op x y =A function named do_math is defined that accepts an operator and two operands. The function's return value will be either the sum or difference of the operands, depending on whether the given operator is + or -. Notice how do_math is then called with a single argument. OCaml doesn't raise an error; it simply returns a function that "remembers" the first argument and accepts the remaining two arguments later (this is an over-simplified and slightly inaccurate statement, but a good enough description for our purpose here). This intermediate function can be used elsewhere, as in the bindings for inc and dec.
match op with
'+' -> x + y
| '-' -> x – y
| _ -> failwith "Invalid op"
let add = do_math '+'
let inc = add 1
let dec = add (-1)
;;
Now here's a version of the do_math() function in PHP:
function do_math($op, $x, $y) {
switch ($op) {
case '+':
return $x + $y;
case '-':
return $x - $y;
default:
throw new Exception("Invalid op");
}
}
Unfortunately, PHP will throw warnings if you call do_math() without the three arguments it expects.Warning: Missing argument 2 for do_math(), called in /home/tboronczyk/curry.php on line 16 and defined in /home/tboronczyk/curry.php on line 2
Warning: Missing argument 3 for do_math(), called in /home/tboronczyk/curry.php on line 16 and defined in /home/tboronczyk/curry.php on line 2
Whereas functional languages have currying "built-in," you must explicitly code this ability in an imperative language. Doing so in PHP requires the use of closures:
function do_math($op) {
return function ($x) use ($op) {
return function ($y) use ($op, $x) {
switch ($op) {
case "+":
return $x + $y;
case "-":
return $x - $y;
default:
throw new Exception("Invalid op");
}
};
};
}
It's also possible to extend the function using func_num_args() and func_get_arg() functions, anonymous functions, and closures, so that any number of parameters can be given at a time. This is a more general form of currying known as "partial functions".function do_math() {
if (func_num_args() >= 1) $op = func_get_arg(0);
if (func_num_args() >= 2) $x = func_get_arg(1);
if (func_num_args() == 3) $y = func_get_arg(2);
switch (func_num_args()) {
case 1:
return function () use ($op) {
if (func_num_args() >= 1) $x = func_get_arg(0);
if (func_num_args() == 2) $y = func_get_arg(1);
switch (func_num_args()) {
case 1:
return function ($y) use ($op, $x) {
return do_math($op, $x, $y);
};
case 2:
return do_math($op, $x, $y);
default:
trigger_error(
"invalid argument count",
E_USER_WARNING);
}
};
case 2:
return function ($y) use ($op, $x) {
return do_math($op, $x, $y);
};
case 3:
switch ($op) {
case "+":
return $x + $y;
case "-":
return $x - $y;
default:
throw new Exception("Invalid op");
}
default:
trigger_error("invalid argument count",
E_USER_WARNING);
}
}
It's messy... but it works! Now you are able to pass one or two arguments to do_math(), capture the intermediate function that's returned, and pass the remaining argument(s) later.$add = do_math("+");
$inc = $add(1);
$dec = $add(-1);
echo do_math("-", 3, 2);
echo do_math("+", 1, 1);
echo $inc(2);
echo $add(2, 2);
echo $dec(6);
echo $add($inc(4), $dec(2));
The switch statements are rather unmanageable and the spaghettification of code grows exponentially with the addition of each argument. This pattern is straight forward, though. You may want to consider writing a code generator to handle the dirty work of retrofitting a function to one capable of being curried rather than writing them all by hand. Of course, if you know of a better way to curry functions in PHP then let me know by leaving a comment!Update 06/29/09: Someone asked me what the "real-world use" for all this would be. Currying is used all the time in functional programming, but the hassle of explicitly enabling the behavior in PHP makes that a valid question. My motivation was just to see if it were possible and share my results. Indeed it is. Functions can be curried in any language that supports closures. But for those who want something a little more concrete, let's consider callback functions.
In a previous post I gave the following example to illustrate the use of closures:
$userPercent = 0.5;It showed an anonymous function being used with array_filter() to filter an array. The array is filtered based on a dynamic value, and a closure is used to "inject" the threshold rather than using a global statement. The same could also be accomplished with currying.
$userList = array_filter($percentVowels,
function($percent) use ($userPercent) {
return ($percent >= $userPercent);
});
The problem is array_filter() expects a callback function that accepts one argument--the current array element. Currying will allow us to prepare the function with the sorting threshold, and the intermediate function can be used as the callback.
function callback($userPercent) {
return function($percent) use ($userPercent) {
return ($percent >= $userPercent);
};
}
$userList = array_filter($percentVowels, callback(0.5));