Partial Application & Currying in PHP

Posted on by Chris Warburton

History

It’s been a while since I wrote about [currying] 1 in [Javascript] 2 and I’ve been meaning to do the same in PHP. I’ve had a prototype PHP implementation knocking around from the same time as my Javascript one, but I was never happy with it, specifically because it took a function’s parameter number explicitly. In other words, whereas in Javascript we can do this:

var my_func = curry(function(x, y) { return x + y; });

In PHP I would have to do this:

$my_func = $curry(2, function($x, $y) { return $x + $y; });

Note the explicit 2, which tells $curry how many parameters to accumulate before calling the function. I’ve since revisited the code and removed this restriction, as well as implementing a couple of alternatives.

Context

Code Smells, DRY and Expressions

Suppose we want to do a bunch of processing to a value. One way we might do it is imperatively, like this:

$val2 = $function1($val );
$val3 = $function2($val2);
$val4 = $function3($val3);
$val5 = $function4($val4);
$val6 = $function5($val5);
$val7 = $function6($val6);
$val8 = $function7($val7);
return  $function8($val7);

This is clearly a mess, since there’s a very repetitive structure and we’ve got all kinds of intermediate results hanging around. It does the job, but this sort of redundancy and bloat can make larger, more complicated projects balloon in size and become difficult to understand.

There are a few ways we could improve this. For example, we might loop over the functions:

foreach ([$function1, $function2, $function3, $function4,
          $function5, $function6, $function7, $function8] as $function) {
  $val = $function($val);
}
return $val;

That’s much cleaner, but now we’re clobbering the contents of $val. We’re trying to calculate a value, but we’re using statements to do it. Statements are PHP’s way of sequencing side-effects (like variable assignment), but we should be keeping side-effects to a minimum if we want our code to be easy to understand and reusable. To calculate values we should instead use expressions.

We can easily turn a loop statement into an expression by asking what it is the loop is doing. In this case, we’re using the loop to turn a collection of values (the array of functions) into a single value (the result of sending $val through them). This is known as a fold or a reduction, so we can use PHP’s [array_reduce] 3 function:

$functions = [$function1, $function2, $function3, $function4,
              $function5, $function6, $function7, $function8];

return array_reduce($functions,
                    function($result, $function) {
                      return $function($result);
                    },
                    $val);

Here we’ve managed to get rid of most statements, but we’ve had to write a whole new function (the second argument to array_reduce) for something which seems pretty basic: passing a value into a function. At least if we pull out this function definition, we won’t have to redefine such a simple thing ever again:

$pass_into = function($x, $f) { return $f($x); }

return array_reduce($functions, $pass_into, $val);

That array_reduce now looks nice and simple, and we’ve gained a reusable library function in the process.

In fact PHP does have a function very similar to $pass_into, known as [call_user_func] 4. Unfortunately it takes its arguments the other way around:

$pass_into = function($x, $f) { return call_user_func($f, $x); }

return array_reduce($functions, $pass_into, $val);

Since we always want our code to be as generic as possible, we’re actually better off abstracting $pass_into into a more general-purpose argument-flipping function, giving the final result of:

$flip1 = function($f) {
  return function($x, $y) use ($f) { return $f($y, $x); };
};

$functions = [$function1, $function2, $function3, $function4,
              $function5, $function6, $function7, $function8];

return array_reduce($functions, $flip1('call_user_func'), $val);

Where $flip1 is a general function which can be put into a library for reuse elsewhere.

Flipping Awkwardness

Notice that our ‘flip’ function has a special structure: we accept one argument $f, then we return a function which accepts another two arguments $x and $y, before finally calling $f with $y and $x.

Why didn’t we use any of the following definitions instead?

// Accepts $f, $x and $y at the same time
$flip2 = function($f, $x, $y) { return $f($y, $x); };

// Accepts $f and $x then waits for $y
$flip3 = function($f, $x) {
  return function($y) use ($f, $x) { return $f($y, $x); };
};

// Accepts $f and waits for $x, then waits for $y
$flip4 = function($f) {
  return function($x) use ($f) {
    return function($y) use ($f, $x) { return $f($y, $x); };
  };
};

// etc.

It turns out that our definition of $flip1 coincides exactly with the way array_reduce handles its arguments, but the other $flip functions don’t. However, there’s nothing inherently wrong with any of those other definitions; they may be perfectly appropriate in different circumstances.

So, should we keep all of them just in case? Well if we do, we will need to consider even more cases: notice that in all of the definitions above we are getting our result by calling $f($y, $x), but what if $f doesn’t take both of its arguments at once? In particular, many of these $flip functions don’t take their arguments all at once, so we need even more $flip functions to handle all of these possible calling conventions:

$flip5 = function($f, $x, $y) {
  return call_user_func($f($y), $x);
};

$flip6 = function($f) {
  return function($x, $y) use ($f) {
    return call_user_func($f($y), $x);
  };
};

$flip7 = function($f) {
  return function($x) use ($f) {
    return function($y) use ($f, $x) {
      return call_user_func($f($y), $x);
    };
  };
};

// etc.

Clearly we’re going too far down a rabbit hole here. How about we step back and consider a different solution, rather than writing more and more code to try and cover every possibility?

Partial Application

Real-World Example

We’ll start with a simple, but rather weak, solution: partial application. Recently at work I wrote a helper function to recursively insert a value at a given location in an array/object, something like the following:

// Usage:         path             value collection
$x = array_insert(['a', 'b', 'c'], 42,   ['a' => new stdClass]);

// Result
$x['a']->b['c'] === 42

// In fact it's the inverse of an existing 'lookup' function, such that:
lookup(array_insert($path, $value, $collection), $path) === $value;

An implementation of array_insert is given below:

function array_insert($path, $val, $ao = array()) {
  if (!$path) return $val;

  $i = array_shift($path);

  if (is_object($ao)) {
    $ao->{$i} = array_insert($path,
                             $val,
                             isset($ao->{$i})? $ao->{$i} : array());
  }
  else {
    $ao  [$i] = array_insert($path,
                             $val,
                             isset($ao  [$i])? $ao  [$i] : array());
  }
  return $ao;
}

Clearly there is redundancy here, since the if/else branches both call array_insert with $path and $val. This violates [DRY] 6 (Don’t Repeat Yourself), but it’s difficult to see how we could remove the redundancy since it’s a recursive call.

In fact, the way I solved this was to use partial application:

function array_insert($path, $val, $ao = array()) {
  if (!$path) return $val;

  $i = array_shift($path);
  $f = papply('array_insert', $path, $val);  // Partial application

  if (is_object($ao)) $ao->{$i} = $f(isset($ao->{$i})? $ao->{$i} : array());
  else                $ao  [$i] = $f(isset($ao  [$i])? $ao  [$i] : array());

  return $ao;
}

So what’s going on here? Here’s the papply function:

function papply() {
  $args = func_get_args();
  return function() use ($args) {
    return call_user_func_array('call_user_func',
                                array_merge($args, func_get_args()));
  };
}

papply will accept any number of arguments, since it uses [func_get_args] 7, then it returns a function also accepting any number of arguments. These two argument arrays are merged together and sent to call_user_func.

Detailed Example

Here’s a trivial example of using papply:

$replace_amps = papply('str_replace', '&', 'and');

The resulting closure, in this case called $replace_amps, has access to all of the arguments we gave, in this case 'str_replace', '&' and 'and'.

Now we can pass some more arguments to this closure:

$my_string2 = $replace_amps($my_string1);

In this example, the merged arguments will be ['str_replace', '&', 'and', $my_string1]. The closure uses [call_user_func_array] 8 to pass these as the arguments to another function (a process known as [uncurrying] 9). It just-so-happens to pass them all to call_user_func, which performs a regular function call. Let’s step through its execution:

$my_string2 = $replace_amps($my_string1);

// Substitute in the definition of $replace_amps
$my_string2 = call_user_func(
  papply('str_replace', '&', 'and'),
  $my_string1);

// Substitute in the definition of papply
$my_string2 = call_user_func(
  call_user_func(
    function() {
      $args = func_get_args();
      return function() use ($args) {
        return call_user_func_array(
          'call_user_func',
          array_merge($args, func_get_args()));
      };
    },
    'str_replace', '&', 'and'),
  $my_string1);

// Apply the inner function
$my_string2 = call_user_func(
  function() {
    return call_user_func_array(
      'call_user_func',
      array_merge(['str_replace', '&', 'and'], func_get_args()));
  },
  $my_string1);

// Apply the remaining function
$my_string2 = call_user_func_array(
  'call_user_func',
  array_merge(['str_replace', '&', 'and'], [$my_string1]));

// Apply array_merge
$my_string2 = call_user_func_array(
  'call_user_func',
  ['str_replace', '&', 'and', $my_string1]);

// Apply call_user_func_array
$my_string2 = call_user_func(
  'str_replace', '&', 'and', $my_string1);

// Apply call_user_func
$my_string2 = str_replace('&', 'and', $my_string1);

As you can see, the first argument we gave to papply ('str_replace') will be called with the remaining arguments ('&', 'and') and the arguments we gave to the resulting closure ($mystring1).

If we step through the evaluation of array_insert, we see the same thing happening:

if (is_object($ao)) $ao->{$i} = $f(isset($ao->{$i})? $ao->{$i} : array());
else                $ao  [$i] = $f(isset($ao  [$i])? $ao  [$i] : array());

// Substitute in the definition of $f
if (is_object($ao)) $ao->{$i} = call_user_func(
  papply('array_insert', $path, $val),
  isset($ao->{$i})? $ao->{$i} : array());
else $ao[$i] = call_user_func(
  papply('array_insert', $path, $val),
  isset($ao[$i])? $ao[$i] : array());

// Substitute in the definition of papply
if (is_object($ao)) $ao->{$i} = call_user_func(
  function() {
    return call_user_func_array(
      'call_user_func',
      array_merge(['array_insert', $path, $val], func_get_args()));
    };
  },
  isset($ao->{$i})? $ao->{$i} : array());
else $ao[$i] = call_user_func(
  function() {
    return call_user_func_array(
      'call_user_func',
      array_merge(['array_insert', $path, $val], func_get_args()));
    };
  },
  isset($ao[$i])? $ao[$i] : array());

// Apply call_user_func
if (is_object($ao)) $ao->{$i} = call_user_func_array(
  'call_user_func',
  array_merge(['array_insert', $path, $val],
              [isset($ao->{$i})? $ao->{$i} : array()]));
else $ao[$i] = call_user_func_array(
  'call_user_func',
  array_merge(['array_insert', $path, $val],
              [isset($ao[$i])? $ao[$i] : array()]));

// Apply array_merge
if (is_object($ao)) $ao->{$i} = call_user_func_array(
  'call_user_func',
  ['array_insert', $path, $val,
   isset($ao->{$i})? $ao->{$i} : array()]);
else $ao[$i] = call_user_func_array(
  'call_user_func',
  ['array_insert', $path, $val,
   isset($ao[$i])? $ao[$i] : array()]);

// Apply call_user_func_array
if (is_object($ao)) $ao->{$i} = call_user_func(
  'array_insert', $path, $val,
  isset($ao->{$i})? $ao->{$i} : array());
else $ao[$i] = call_user_func(
  'array_insert', $path, $val,
  isset($ao[$i])? $ao[$i] : array());

// Apply call_user_func
if (is_object($ao)) $ao->{$i} = array_insert(
  $path, $val, isset($ao->{$i})? $ao->{$i} : array());
else $ao[$i] = array_insert(
  $path, $val, isset($ao[$i])? $ao[$i] : array());

We’ve recovered the original, clunky code :)

Calling Conventions and Partial Application

In particular, notice that if we have a function which takes two arguments, like $f = function ($x, $y) { ... }, then papply lets us give each argument in a separate step, e.g.

$g = papply($f, $x);
$g($y);

We can go even further by splitting the papply($f, $x) call into two separate steps as well, using another call to papply:

$h = papply('papply', $f);

The resulting $h function will accept one set of arguments (e.g. $x), then another set of arguments (e.g. $y), just as if it were defined as function($x) { return function($y) use ($x) { ... }; }.

Turning a function with one set of arguments into a function with two sets of arguments is really useful, so we can make a generic function to do this transformation. How? By using papply again!

$splitArgs = papply('papply', 'papply');

With this general-purpose function, the above simplifies to:

$h = $splitArgs($f);

Simple Flipping

To see why this function is so useful, think back to our proliferation of $flip functions: the reason we needed so many was because their calling conventions differed in exactly the ways that $splitArgs can convert between. With papply and $splitArgs in our tool box, we can massively simplify the $flip functions:

// This is the only $flip function we need for $f($y, $x)
$flip = function($f, $x, $y) { return $f($y, $x); };

// We can implement the others via simple transformations

$flip1 = $splitArgs($flip);

$flip2 = $flip;

$flip3 = $flip1;

$flip4 = $splitArgs($flip1);

// This is the only $flip we need for call_user_func($f($y), $x)
$flip5 = function($f, $x, $y) {
  return call_user_func($f($y), $x);
};

// The others are simple transformations

$flip6 = $splitArgs($flip5);

$flip7 = $splitArgs($flip6);

Advantages of Partial Application

One of the reasons we still need two forms of $flip is because these partially-applied functions are much weaker than proper curried ones which I’ll explain further down. On the other hand, their advantage is predictability: each call to papply will delay a function call exactly once, which means a single closure will be generated. This is important in PHP since we don’t have [tail-call optimisation] 10, so we always have to worry about overflowing the stack.

Another place where this can be especially useful is in our [array combinators] 11. For example, we can turn the following:

$a = array_map(function($x) { return str_replace('&', 'and', $x); },
               $my_array1);

$b = array_filter($my_array2,
                  function($x) { return preg_match('/[0-9]+/', $x); });

Into:

$a = array_map(papply('str_replace', '&', 'and'), $my_array1);

$b = array_filter($my_array2, papply('preg_match', '/[0-9]+/'));

This is known as [eta-reduction] 12, and it turns up all over the place in PHP. For example, today I was reading the documentation for [Silex] 13. Here are some of their examples which can be easily eta-reduced:

// Original
function ($id) { return (int) $id; }

// Eta-reduced
'intval'

// Original
function () use ($app) {
  return $app->redirect('/hello');
}

// Eta-reduced
papply([&$app, 'redirect'], '/hello')

// Original
function () use ($app) {
  // redirect to /hello
  $subRequest = Request::create('/hello', 'GET');

  return $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}

// Eta-reduced
papply([&$app, 'handle'], Request::create('/hello', 'GET'),
                          HttpKernelInterface::SUB_REQUEST)

// Original
function () use ($file) {
    readfile($file);
}

// Eta-reduced
papply('readfile', $file)

Proper Currying

Using papply at work prompted me to have another stab at a more general currying function, since I realised I could use [reflection] 14 to overcome the parameter number issue. We can still use the $curry function shown near the beginning, with its explicit parameter number, but I’ve renamed it to $curry_n (ie. ‘curry N parameters’). The reflective replacement is simply:

$curry = $curry_n(1, function($f) use ($curry_n) {
  return $curry_n(call_user_func([new ReflectionFunction($f),
                                  'getNumberOfParameters']), $f);
});

The $curry function will take a function $f and pass it to $curry_n, looking up the parameter number via reflection. Even though the $curry function only takes 1 parameter, we also curry it for reasons explained in the Returning Functions section below.

As for the definition of $curry_n, it’s unfortunately a little complicated:

// Curries n-argument functions
$curry_n = call_user_func(function() {
    // An uncurried version of $curry_n, encapsulated in this lambda
    $_curry_n = function($n, $f) {
        // An acceptor gathers $n arguments then calls $f
        $make_acceptor = function($args) use ($n, $f, &$make_acceptor) {
            return function() use ($n, $f, $args, &$make_acceptor) {
                $args2 = array_merge($args, func_get_args());
                if (count($args2) >= $n) {
                    // Send $n args to $f
                    $ret = call_user_func_array($f, array_slice($args2, 0, $n));
                    // Send any remaining args to the return value
                    return (count($args2) > $n)
                      ? call_user_func_array($ret, array_slice($args2, $n))
                      : $ret;
                }
                // Otherwise make an acceptor storing these arguments
                return $make_acceptor($args2);
            };
        };
        // A curried function is just an acceptor without any args
        return $make_acceptor([]);
    };

    // Only expose a curried version of $curry_n
    return $_curry_n(2, $_curry_n);
});

Motivation

So what does currying get us, compared to not currying? (I’ll ignore partial application until the Partial Application vs Currying section) Currying removes the arbitrary distinction PHP makes between functions with ‘different numbers of arguments’.

Like with the different $flip functions above, PHP will distinguish between functions like these three:

$foo1 = 'foo';

$foo2 = function($x, $y) {
          return foo($x, $y);
        };

$foo3 = function($x) {
          return function($y) use ($x) {
            return foo($x, $y);
          };
        };

These all call the foo function, but they differ in how they take their arguments:

I discuss $foo1 in the Taking All The Arguments section; for now let’s compare $foo2 with $foo3.

The first thing to spot is that we can replace every occurrence of $foo2($a, $b) with call_user_func($foo3($a), $b), yet we cannot replace occurrences of $foo3 using $foo2. We need some sort of wrapper, but that’s exactly what $foo3 already is!

Hence out of these two options, we should always prefer to use $foo3, since we can easily transform it into $foo2 if needed.

Unfortunately, functions like $foo3 cause a few problems:

  1. The amount of boilerplate is greatly increased, since every abstraction must be delimited with the function and return keywords and we must explicitly list all of our free variables with use.
  2. Applying several abstractions at the same time is painful, since PHP’s lexer doesn’t accept chained function calls, ie. $foo3(10)(20), which forces us to reify each application with call_user_func.
  3. There are pretty much no PHP libraries/frameworks/etc. which do this, so we would be forced to switch back and forth between each convention.

These problems would all be solvable if PHP had some kind of abstraction mechanism to encapsulate duplicate code in a reusable component. Thankfully it does, they’re called functions! In fact the $curry and $curry_n functions defined above solve all of these problems!

$curry

The $curry function converts an uncurried function like $foo2 into a curried form like $foo3. This solves half of problem (3), since it lets us curry existing libraries and frameworks whenever we like.

Rather than accepting one argument at a time, like $foo3, using $curry and $curry_n actually lets us give any number of arguments at a time, including all of them at once. In other words, we can still use PHP’s regular calling convention $blah($x, $y, $z) for curried functions. This solves problem (2) since we don’t need to use call_user_func to supply extra arguments, and it also solves the remaining half of problem (3) since existing libraries and frameworks will work perfectly well if we give them curried functions.

What’s more, the $curry and $curry_n functions allow us to write our own functions in an non-curried style (avoiding problem (1)), which we can then pass through $curry to get the behaviour we want.

For example, let’s say we need some arithmetic functions. PHP doesn’t have any of the basic functions, so we need to write our own:

$curry_all = papply('array_map', $curry);

list($sum, $product, $subtract, $divide) = $curry_all([
  function($x, $y) { return $x + $y; },
  function($x, $y) { return $x * $y; },
  function($x, $y) { return $x - $y; },
  function($x, $y) { return $x / $y; }]);

These definitions are non-curried, like $foo2, since they take two arguments and don’t have any inner functions, but by passing them through $curry we’ve curried them to behave like $foo3, accumulating arguments until they have two. For example:

// Apply an argument to each
$increment  = $sum(1);
$negate     = $subtract(0);
$double     = $product(2);
$reciprocal = $divide(1);

// Some invariants
list($increment_test,
     $negate_test,
     $double_test,
     $reciprocal_test) = $curry_all([
       function($x) use ($increment) {
         return $increment($x) === 1 + $x;
       },
       function($x) use ($negate) {
         return $negate($x) === 0 - $x;
       },
       function($x) use ($double) {
         return $double($x) === 2 * $x;
       },
       function($x) use ($reciprocal) {
         return $reciprocal($x) === 1 / $x;
       }]);

// We can still use the regular calling convention if we like
$sum     (10, 20) === 30;
$product (3 , 4 ) === 12;
$subtract(10, 3 ) === 7;
$divide  (10, 2 ) === 5;

What the $curry function does, as mentioned above, is pass its first argument to $curry_n along with its number of named parameters (ie. explicit arguments). It turns out we don’t always want to use this number, since many PHP functions use more (via func_get_args) or fewer (via default values). In these cases we can call $curry_n directly.

$curry_n

The $curry_n function is the heart of the $curry function but can also be used standalone. It accepts a number $n and a function $f which it encapsulates in a closure, along with array $args of arguments which is initially empty. These closures are “acceptors” which only exist to accept arguments.

If we ever call an acceptor, we will receive back another acceptor encapsulating the same $n and $f, but its $args array will also have all of the arguments we passed in (if any) pushed on to the end. Note that the original acceptor is not affected in any way; the extra arguments are only stored in the new acceptor we received back.

This new acceptor will behave exactly like the first, except for the extra elements in its $args array. If we pass it some more arguments, we get back another new acceptor which has those arguments pushed on to the end of its own $args array, and so on.

We can keep calling these acceptors as many times as we like, in any order, passing in any amount of arguments we like. When any of the acceptors manage to accumulate $n arguments, those arguments will be sent to the function $f and the result will be returned (instead of a new acceptor).

Whenever $f is called, nothing about any of the acceptors changes. We can carry on calling them in any order with any number of arguments, just like before.

A couple of reasons why we might want to use $curry_n instead of $curry are:

If we didn’t use $curry_n for these cases, we would either end up calling our function without enough parameters, or else we would get back another acceptor when we expect a result.

Here’s an example of $curry_n, where we want to implode $n strings:

// Some helper functions

// Takes an argument $n, then wraps the next $n arguments in an array.
// We use $curry_n on the inner function since it uses func_get_args.
$array = $curry(function($n) use ($curry_n) {
                  return $curry_n($n, function() { return func_get_args(); });
                });

// Takes an $n and composes a function $f with an $n-ary function $g.
// We use $curry_n on the inner function since it uses func_get_args.
$compose_n = $curry(function($n, $f, $g) use ($curry_n) {
                      return $curry_n($n, function() use ($f, $g) {
                        return $f(call_user_func_array($g, func_get_args()));
                      });
                    });

// Takes $n arguments and implodes them with spaces
$space_maker = $curry(function($n) use ($compose_n, $curry, $array) {
                        return $compose_n($n, $curry('implode', ' '),
                                              $array($n));
                      });

// Call $space_maker(10) in various equivalent ways

$sm_            = $space_maker(10);
$sm0            = $space_maker(10, 'zero');
$sm0123         = $sm0('one', 'two', 'three');
$sm_012345      = $sm_('zero', 'one', 'two', 'three', 'four', 'five');
$sm0123456      = $sm0123('four', 'five', 'six');
$sm_012345_     = $sm_012345();
$sm_0123456789  = $sm_('zero', 'one', 'two', 'three', 'four', 'five',
                       'six', 'seven', 'eight', 'nine');
$sm0123456789   = $sm0123456('seven', 'eight', 'nine');
$sm_012345_6789 = $sm_012345_('six', 'seven', 'eight', 'nine');

echo "$sm0123456789\\n";
echo "$sm_0123456789\\n";
echo "$sm_012345_6789\\n";
echo $space_maker(10, 'zero', 'one', 'two', 'three', 'four', 'five',
                  'six', 'seven', 'eight', 'nine');

This will print:

zero one two three four five six seven eight nine
zero one two three four five six seven eight nine
zero one two three four five six seven eight nine
zero one two three four five six seven eight nine

Since the $space_maker function is curried, we’re free to call it however we like and as long as it eventually gets enough arguments (10 in this case) it will produce the imploded result.

Partial Application vs Currying

Currying is very similar to partial application shown above:

However, currying is strictly more powerful:

There is one more trick to currying compared to partial application, and that’s what happens when we give too many arguments.

Returning Functions

So far we’ve seen that $curry and $curry_n can automatically turn a function like this:

$bar1 = function($x, $y) { return $x + $y; };

Into a function like this:

$bar2 = function($x) {
          return function($y) use ($x) { return $x + $y; };
        };

However it converts the other way too! Our curried functions are much more powerful than $bar1 or $bar2, since they let us use PHP’s regular calling convention $blah($x, $y) on both.

Specifically if your curried function returns another function, it will all be treated as one. Here’s an example:

$output_if_both_positive = $curry(function($x, $y) {
  return ($x > 0 && $y > 0)? function() {
                               var_dump(func_get_args());
                               return TRUE;
                             }
                           : FALSE;
});

// Returns a closure
$output_if_both_positive(10);

// Returns FALSE
$output_if_both_positive(10, -4);

// Outputs ['hello', 'world'] and returns TRUE
$output_if_both_positive(10, 20, 'hello', 'world');

Note that this will work even if the returned function is not curried, since it will be passed all remaining arguments. However, it is especially useful when your returned functions are curried, because in that case they will only take as many arguments as they need and will pass the rest on to the next returned function.

This makes our life an awful lot simpler, since we no longer need to care about using call_user_func, remembering which parameters need to go to which function, etc. We just need to curry all of our functions when we define them, then throw all of our arguments to the first function we call and it will send them all to the right place. This is another reason why it’s important to set the right number of parameters, eg. via $curry_n; if we don’t, our acceptors won’t know which arguments should be sent to their encapsulated function $f and which should be sent to its return value.

Taking All The Arguments

There is one unanswered question: how do we implement $foo1 if all of our functions are curried? Specifically, how do we accept all subsequent arguments?

This sounds tricky, but it’s actually very simple. As far as functions like $foo1 are concerned, only the first batch of arguments matters, since $foo1 is not curried so it can’t accept any more batches. Of course, foo could be curried, but then there would be no problem ;)

All we need to do is wrap our curried n-ary function in a non-curried function:

// If we're given a curried function like this
$foo4 = $curry('foo');

// We can wrap it in a non-curried function like this
$foo5 = function() use ($foo4) {
          return call_user_func_array($foo4, func_get_args());
        };

$foo5 will accept all of the arguments it is given when called, then pass them on to $foo4 which will either:

What if we want to pass all of our (first batch of) arguments directly to foo, but we would like to behave like a curried function thereafter? In that case we just need to do the currying once we’ve got our arguments:

$foo6 = function() use ($curry_n) {
          $args = func_get_args();
          return call_user_func_array($curry_n(count($args), 'foo'),
                                      $args);
        };

Drawbacks of Currying

Besides trying to further simplify the currying implementation itself, there are still some issues with this.

It’s nice that curried functions ‘take care of themselves’, but this makes their stack usage a bit more difficult to predict, especially if we’re currying all of our functions as we define them. As mentioned above, this is a problem since PHP doesn’t do tail-call optimisation. Some ways our stack usage may increase unnecessarily are:

Since PHP treats named functions differently to lambdas, it is more difficult than I’d like to curry a named function. In particular, we have to explicitly eta-expand the definitions of our named functions, in order to handle our arguments properly. For example, something like this:

function curry_n() {
  $_curry_n = function($n, $f) {
    $make_acceptor = function($args) use ($n, $f, &$make_acceptor) {
      return function() use ($n, $f, $args, &$make_acceptor) {
        $args2 = array_merge($args, func_get_args());
        if (count($args2) >= $n) {
          $ret = call_user_func_array($f, array_slice($args2, 0, $n));
          return (count($args2) > $n)
            ? call_user_func_array($ret, array_slice($args2, $n))
            : $ret;
        }
        return $make_acceptor($args2);
      };
    };
    return $make_acceptor([]);
  };

  // Call $_curry_n(2, $_curry_n) with our arguments; ie. we're an
  // eta-expansion of $_curry_n(2, $_curry_n)
  return call_user_func_array(
    $_curry_n(2, $_curry_n), func_get_args());
}

function curry() {
  // Call curry_n(...) with our (remaining) arguments, ie. we're an
  // eta-expansion of curry_n(...)
  $args = func_get_args();
  $f    = array_shift($args);
  return call_user_func_array(
    curry_n(call_user_func([new ReflectionFunction($f),
                           'getNumberOfParameters']), $f),
    $args);
}

function flip() {
  // Call curry(...) with our arguments, ie. we're an eta-expansion of
  // curry(...)
  return call_user_func_array(
    curry(function($f, $x, $y) { return $f($y, $x); }),
    func_get_args());
}

Of course, the idealist in me would avoid named functions altogether, since they’re global and globals should always be avoided (UPDATE: PHP now has namespaces, so the problem of globals is at least mitigated a little). However, the alternatives are:

For these reasons, I’ve not used this currying code in a production system yet.

On the other hand, I’ve found papply to be extremely useful :)