Skip to content

Instantly share code, notes, and snippets.

@pcrov
Last active February 9, 2019 01:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pcrov/82a3477d04682e2711306f5b12735b35 to your computer and use it in GitHub Desktop.
Save pcrov/82a3477d04682e2711306f5b12735b35 to your computer and use it in GitHub Desktop.

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.

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.

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
)

Open issues

  1. ... in a non-trailing space, e.g. f(..., 1), should be a Parse or Fatal error. Whatever is convenient for the implementer.

Backward Incompatible Changes

None.

Proposed PHP Version(s)

7.4 or 8

Voting Choices

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

Patches and Tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment