Currying PHP

by jumblesale

Let’s make PHP curry!

Since PHP 5.3 we’ve been able to use anonymous functions. Anonymous functions are great because it adds a powerful layer of expressiveness to an otherwise drab and inexpressive language. It means we can do cool things like this:

$callback = function($message) {
    echo $message;
};

function snooze($callback) {
    sleep(10);
    $callback('Wake up!');
}

snooze($callback); // Wake up!

We can also create closures with the use keyword.

$callbacks = array();
for($i = 1; $i <= 10; $i++) {
    array_push($callbacks, function() use ($i) {
        echo "$i\n";
    });
}

foreach($callbacks as $callback) {
    $callback();
}

Which is totally amazing if you need a really convoluted way of remembering how to count to 10.

With these handy new functional tools we can add currying to PHP. Some background: currying is a functional programming method named for actor Tim Curry who fell in love with Haskell during the filming of Congo in 1995. It’s a way of using functions returning functions to make code more expressive. If you’re not familiar with it, I’ll let Crockford explain it.

Fortunately for Crockford, JavaScript has a healthy basis in functional programming. Fortunately for us, 5.3 goes just far enough that we can implement it in PHP.

function curry($fn, $arg) {
    return function() use ($fn, $arg) {
        $args = func_get_args();
        array_unshift($args, $arg);
        return call_user_func_array($fn, $args);
    };
}

Really straightforward. You pass it a function and the argument you want to bake in. It forms an array where the first element is the baked-in value and the other values are whatever other arguments get passed to the function. func_get_args is a ridiculously-named but inarguably helpful function in PHP core which gives us every argument passed to the current function as an array. array_unshift is used to prepend our baked-in value to that array. call_user_func_array finally unpacks the array and passes it to $fn as a list of arguments. This all gets closured up and returned to the caller. Let’s see how we can use it:

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

$add1 = curry('add', 1);

echo $add1(2, 3); // 6

It works. Try it out! We create a new function reference, $add1, which uses the add function but sets $a to always be 1. But what if we want to take this a little further and pass in a function instead of a function name?

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

$add2 = curry($add, 2);

echo $add2(4, 5); // 11

Notice how we pass $add? Turns out that works too. call_user_func_array totally has your back on this one. It doesn’t care if it’s a string, a closure, whatever, it just gets on with it. But what if we want to (and to be honest, this is going to be about 99% of the cases in any real PHP program) call it with a class method?

This is where call_user_func_array lets us down a little bit. No worries, we can work around it by editing our curry function just a little:

function curry($fn, $arg, $obj = null) {
    return function() use ($fn, $arg, $obj) {
        $args = func_get_args();
        array_unshift($args, $arg);
        if($obj) {
            $fn = array($obj, $fn);
        }
        return call_user_func_array($fn, $args);
    };
}

So we allow an optional object to be passed and if that exists, we create the closure around that object’s method. Here’s how we’d use it:

class Adder {
    public function add($a, $b, $c) {
        return $a + $b + $c;
    }
}

$add3 = curry('add', 3, new Adder());

echo $add3(6, 7); // 16

Most of the time you’re going to be passing $this into it so you can curry a class’s own methods. This is where currying actually came in handy for me recently: I was working with a class method which took two parameters, from and to for a source and a destination. Most of the time I was using the same source but having to pass a reference to that resource in an ugly way. Not so with currying! I just curry the function with from baked in, give it a more meaningful name and then just reference that new method from there on.

So there we go, the best PHP curry this side of Tooting. Next up: partials.

Advertisements