PHP 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 placeholder ?. 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.

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 used, 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

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);

func_num_args et al.

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

Extra Arguments

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) {
$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:

    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4

Trailing Placeholders

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);

Extra Trailing Placeholders

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

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) {
$f = f(?, 2, ?, 4, ?, 6);
$f(1, 3);

Would output:

    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 6

Use as an identifier

Although not the primary goal, this feature would also provide a way to reference a function as a callable if needed in a given context. Specifically, a function that partials all of its parameters would result in a closure that is functionally identical to the original function. For example:

function by5(int $x) { return $x * 5; }
$arr = [1, 2, 3, 4, 5];
$result = array_map(by5(?), $arr);

That would make such functions still accessible to refactoring and static analysis tools, while avoiding any new syntax.

Backward Incompatible Changes


Proposed PHP Version(s)

Next major/minor

rfc/partial_function_application.txt · Last modified: 2020/07/01 16:40 by crell