rfc:function_referencing

PHP RFC: Function Referencing as Closures

Introduction

For the longest time, PHP has lacked first-class functions. Because of the separate namespaces, functions and methods can't be dealt with directly like other arguments, and you must refer to them with strings or arrays. This means that references to functions are not necessarily obvious, as they simply look like a normal string or array, and cannot be validated until call-time or passed to a function with a callable typehint. This can make code harder to read unless you are familiar with a function's signature, for both humans and machines.

PHP 5.3 added anonymous functions (or closures), which support nesting, can close over both variable and class scopes, and can be arbitrarily scoped, bound and applied at runtime. Aside from not directly supporting partial application, PHP's closures are first-class functions. However, they are segregated from standard functions and methods, which do not enjoy the benefits closures receive.

This RFC proposes a new syntax for referencing normal functions and methods as closures, bringing the benefits of first-class functions to them, making references to functions obvious, and allowing immediate validation that functions being referenced exist.

Proposal

Overview

A function can be referenced with the & operator, returning a closure:

$func = &strlen;
array_map($func, ['foo', 'foobar', 'elePHPant']); // [3, 6, 9];

As it is an ordinary expression, it can be used inline:

array_map(&strlen, ['foo', 'foobar', 'elePHPant']); // [3, 6, 9];

This works also for static methods and methods (here we use the ->call method for convenience, which this RFC would add):

class FooBar {
    private $x;
    public function __construct($x) { $this->x = $x; }
    public function get() { return $this->x; }
    public static function getStatic(FooBar $obj) { return $obj->x; }
}
 
$qux = new FooBar(3);
 
// Static functions referenced retain their scope
$func = &FooBar::getStatic;
// Thus it can see the instance variables of $qux
$func($qux); // 3
 
$func = &FooBar::get;
$func->call($qux); // 3
 
// Or, if we wish to bind
$func = Closure::bind(&FooBar::get, $qux);

Details

The current & syntax used for referencing variables is extended to support functions:

function_reference:
		'&' namespace_name
	|	'&' T_NAMESPACE T_NS_SEPARATOR namespace_name
	|	'&' T_NS_SEPARATOR namespace_name
	|	'&' class_name T_PAAMAYIM_NEKUDOTAYIM variable_name
;

It does not permit dynamic references such as &$classname::foo, due to conflicts with existing syntax and for symmetry (while &$classname::foo would be doable, &FooBar::$foo is not, so we do neither).

When a function is referenced in this manner, an unbound, unscoped Closure of that function is returned. For a static method, a static, scoped Closure is given. When a normal method is referenced, an unbound, scoped Closure is given.

We relax the restriction on unbound scoped closures. This is because if we were to give a static method, it could not be bound (useless as it is an instance method), and we don't know what to bind to ahead-of-time. Thus we create an incomplete closure of sorts, which can be called and probably won't work (much like you can statically call an instance method), or can be bound with ->bindTo or ->call. We don't provide any way to produce an unbound scoped closure in userland as this is, aside from function referencing, an obscure use case.

Because the ->call method would be useful here, this RFC depends on that RFC passing first, and the patch incorporates the ->call patch.

Backward Incompatible Changes

None.

Proposed PHP Version(s)

Next PHP 5.x, or next PHP X (PHP 7), whichever comes first.

Future Scope

There is no future scope to this RFC.

Proposed Voting Choices

As this adds to the language itself, a 2/3 majority is required. A straight Yes/No vote is to be held.

Patches and Tests

A branch which implements this with a test which incorporates the Closure::call patch and is based on master can be found here: https://github.com/TazeTSchnitzel/php-src/compare/function_reference_with_apply

References

rfc/function_referencing.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1