- Version: 1.0
- Date: 2019-02-05
- Author: Paul Crovella, Levi Morrison
- Status: Draft
- First Published at: https://wiki.php.net/rfc/partial_function_application
Partial function application is the process of binding only some of the arguments to a function call and leaving the remainder to be bound a later point. In PHP this would be done by a closure.
Support partial function application via the argument placeholders ?
and ...
. Any function or method call in which one or more of the arguments is an argument placeholder instead results in a closure being returned which wraps the called function, fixing the given (non-placeholder) arguments.
The arity of the closure will be that of the partially applied function minus the number of actual arguments given. Type declarations, parameter names, reference parameters, optional defaults, variadics are all taken on by the closure from the function definition. All arguments given to a closure, including those beyond what are specified in the signature, get passed along to the underlying function.
The basic idea is to use a partial function call to stamp out a copy of the function, sans parameters where fixed arguments have already been given.
Type declarations are retained, as are parameter names (e.g. for reflection.)
function f(int $x, int $y): int {}
$partialFoo = f(?, 42);
is equivalent to
$partial = function(int $x): int {
return f($x, 42);
};
Variable arguments are use
d, and done so by reference if specified in the called function definition.
function f($value, &$ref) {}
$array = ['arg' => 0];
$f = f(?, $array['arg']);
is equivalent to
$ref = &$array['arg'];
$f = function($value) use (&$ref) {
return f($value, $ref);
};
Optional parameters remain optional, inheriting defaults.
function f($a = 0, $b = 1) {}
$f = f(?, 2);
is equivalent to
$partial = function($a = 0) {
return f($a, 2);
};
In order to keep func_num_args
and friends happy, the arguments passed through will only be those up to and including the right-most actually given - whether at call time or during partial application. In other words, trailing optional parameters in the closure will not have their resolved defaults sent to the underlying function.
function f($a = 0, $b = 0, $c = 0, $d = 0) {
echo func_num_args();
}
$f = f(?, 1, ?, ?);
$f(0, 2); // prints 3
$f(); // prints 2
You can pass as many additional arguments as you like when calling a function in PHP, beyond what's specified in the signature. When you pass extra arguments to the closure, or during partial application, they are passed along to the wrapped function.
function f($a, $b) {
print_r(func_get_args());
}
$f = f(?, 2);
is actually akin to:
$f = function($a, ...$extraArgs) {
return f($a, 2, ...$extraArgs);
};
(Though without unnecessary packing/unpacking, or $extraArgs
appearing in reflection.)
So calling: $f(1, 3, 4)
results in:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
)
Continuing on that, it is not required to include placeholders for trailing arguments, though at least one placeholder somewhere is necessary to indicate partial application. This is primarily to ease partial application of functions with long lists of optional parameters.
function f($m, $n, $o, $x = 0, $y = 0, $z = 0) {}
$f = f(1, ?);
is roughly (minding the func_num_args
section above) equivalent to
$f = function($n, $o, $x = 0, $y = 0, $z = 0) {
return f(1, $n, $o, $x, $y, $z);
};
In order to better convey to a reader that more arguments are expected, a trailing ...
may be used in addition to or in lieu of ?
placeholders.
So these are equivalent:
$f = f(?, 1);
$f = f(?, 1, ...);
As are these:
$f = f(1, ?);
$f = f(1, ?, ...);
$f = f(1, ...);
Additional placeholders beyond a function's signature do not cause an error and have no additional impact, though effectively allow you to apply all arguments to a function to be called later.
$n = 1000000;
$log10ofn = log10($n, ?);
is equivalent to
$log10ofn = function() use ($n) {
return log10($n);
}
Variadic functions retain their signatures. Placeholders in a variadic position remain optional in the closure (there are no defaults, places not filled are simply not included in what's sent to the underlying function.) Fixed and placeholder arguments can be interleaved together.
function f(...$args) {
print_r($args);
}
$f = f(?, 2, ?, 4, ?, 6);
$f(1, 3);
Would output:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 6
)
...
in a non-trailing space, e.g.f(..., 1)
, should be a Parse or Fatal error. Whatever is convenient for the implementer.
None.
7.4 or 8
Yes or no, requiring a 2/3 majority to pass.