rfc:partial_function_application

PHP RFC: Partial Function Application

Introduction

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.

Proposal

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.

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.

Types

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

Variables/References

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

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

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

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

Would output:

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 6
)

Backward Incompatible Changes

None.

Proposed PHP Version(s)

7.4 or 8

Vote

Yes or no, requiring a 2/3 majority to pass.

rfc/partial_function_application.txt · Last modified: 2019/02/06 11:14 by pcrov