It is possible to determine how many arguments a function accepts by using reflection.
I want to be able to define a function compose
that performs function composition. That is to say, compose($f, $g)
should produce a new function that returns $f($g($x))
.
I have a sample implementation here:
function compose()
{
$fns = func_get_args();
$prev = array_shift($fns);
foreach ($fns as $fn) {
$prev = function ($x) use ($fn, $prev) {
$args = func_get_args();
return $prev(call_user_func_array($fn, $args));
};
}
return $prev;
}
When composing $f
and $g
, $g
may have an arity higher than 1. Which means it can take more than one argument. Thus, the function returned by compose($f, $g)
may also take more than one argument -- it takes exactly the same arguments as $g
.
The problem with this implementation is that there is no way to control the exposed arity of what compose
returns. In this case it is always 1
, because of the function ($x) ...
. When trying to determine the arity using reflection, it will always return 1
instead of that of $g
.
Is there a way to change the amount of arguments of an anonymous function seen by PHP's reflection system, without using eval
and other code generation techniques?
The moment you introduce func_get_args()
into a function should invalidate any hope of being able to determine its true arity. At that point, the arity is really only defined by the function's logic, and cannot be determined by reflection or static code analysis.
I've written a compose
-like implementation before, but it just assumed that the functions you are composing both have an arity of 1.
Based upon How can I dynamically check the number of arguments expected of an anonymous function in PHP? is the following piece of code which works on php >= 5.3
Update: Just reread the question ... this is probably BS
My proposed solution to this would be to introduce a custom convention for specifying the arity using a custom property, defined on an object that wraps the closure.
Like this:
class CustomArityFunction
{
public $f;
public $arity;
function __construct(callable $f, $arity)
{
$this->f = $f;
$this->arity = $arity;
}
function __invoke()
{
return call_user_func_array($this->f, func_get_args());
}
}
// define function
$f = function () { ... };
return new CustomArityFunction($f, $n);
// determine arity
$arity = ($f instanceof CustomArityFunction) ? $f->arity : getReflectiveArity($f);
The major downside of this solution is that the consuming code needs to be aware of the convention.
It is however the cleanest way of doing this that I could come up with.
Note: The wrapper is needed because PHP does not allow assigning properties to closures. Thanks to @nikic for pointing that out.
Here is an ugly solution as it does not work with all number of parameters (or you end up with tons of case
stuff), but it does not rely on eval
:
function compose($f, $g)
{
switch(getReflectiveArity($g)) {
case 1:
return function($x) use ($f, $g) {
return $f($g($x));
};
break;
case 2:
return function($x, $y) use ($f, $g) {
return $f($g($x));
};
break;
case 3:
return function($x, $y, $z) use ($f, $g) {
return $f($g($x, $y, $z));
};
break;
/* ... */
default:
throw new RuntimeException();
}
}