Reuse your closures with functors
Join the DZone community and get the full member experience.
Join For Free- difficult to reuse: if you want to reuse a closure or anonymous function in two different places, you'll have to share the creation code by putting it in a shared class or method. We have no separation between the creation code (which for an object would be a new operator and the code of the closure itself. It's a bit like eval().
- Difficult to force contracts on. You can't type hint a closure: even if a closure takes five arguments and another no arguments at all, you can't differentiate between them as parameters in a method signature.
function takesAsInputAnotherFunction(Closure $closure)
This hack is explicitly stated as an implementation detail which should not be relied upon in the PHP manual
__invoke() to the rescue
What if we wanted instead, a closure which we can instance even more than one time (maybe with different variables), and that we could type hint?
In this case, we need to return to the object-oriented paradigm by defining a class, and then use __invoke() to keep the syntactic sugar of being able to call it very simply:
$object($argument1, $argument2);$object($argument1, $argument2);
This kind of object is called a functor.
The definition of a class is longer than the creation code of a closure, so there is a trade-off: we have better specified code but we lose conciseness. Type hints are not only a defensive programming construct: they serve also as documentation as the next programmer reading a method's signature would learn what he can pass in just from the parameters hints instead of going reading the tests or grepping for the method in his working copy:
grep -r '->methodName(' .
becomes just a quick look at the definition
public function methodName(AdderCallback $callback) { // ...
However the expressing power of an object implementing __invoke() and of a closure are equivalent, and when you find yourself rewriting the same closure over and over, you may want to keep it in only one place. You'll have to create a class to hold the creation code anyway, so why not making it a class of its own?
For the PHP interpreter, closure and functors are really the same thing, and are even more swappable than callbacks built with array($object, 'methodName'). is_callable() returns true for both of them, and you can write $closure() as well as $functor().
Example
Here is some code that you can hack if you want to play with closures and functors.
<?php
$square = function($value) {
return $value * $value;
};
var_dump($square(3));
/**
* a little more noisy, but equivalent in usage and functionality-
*/
class SquareCallback
{
public function __invoke($value)
{
return $value * $value;
}
}
$squareObject = new SquareCallback;
var_dump($squareObject(3));
// using PHP utilities which works on callback: total equivalency
var_dump(is_callable($squareObject));
$array = array(0, 1, 2);
var_dump(array_map($square, $array));
var_dump(array_map($squareObject, $array));
// currying vs collaborators: total equivalency
$toAdd = 5;
$adder = function($value) use ($toAdd) {
return $value + $toAdd;
};
var_dump($adder(3));
/**
* A LOT more noisy
*/
class AdderCallback
{
private $numberToAdd;
public function __construct($numberToAdd)
{
$this->numberToAdd = $numberToAdd;
}
public function __invoke($value)
{
return $value + $this->numberToAdd;
}
};
$adder = new AdderCallback(5);
var_dump($adder(3));
// but only classes can do this
function someMethodOrFunction(AdderCallback $adder)
{
// ...
}
Opinions expressed by DZone contributors are their own.
Comments