rfc:closure_apply

This is an old revision of the document!


PHP RFC: Closure::call

  • Version: 0.2.1
  • Date: 2014-07-29, put to internals 2014-08-03, latest 2014-08-06
  • Author: Andrea Faulds, ajf@ajf.me
  • Status: Under Discussion

Introduction

PHP has had Closures since 5.3, and since 5.4 has had Closure::bind (static method) and Closure::bindTo (method) to allow creating new closures that have $this bound to a specific method. However, it has not been possible to bind at call-time, and you must instead create a temporary new closure, making calling bound to multiple objects cumbersome and inefficient (at least two statements are needed, and a new closure must be created and immediately disposed of for each).

Proposal

A new method is added to Closure, with the following signature:

mixed Closure::call(object $to[, mixed ...$parameters])

It calls the closure with the given parameters and returns the result, with $this bound to the given object $to, using the closure's current scope. Like the bind(To) methods, a static class cannot be bound (using ->call will fail).

It can be used like so:

$foo = new StdClass;
$foo->bar = 3;
$foobar = function ($qux) { var_dump($this->bar + $qux); };
$foobar->call($foo, 4); // prints int(7)

Because the ->call method, unlike bind(To), does not take a scope parameter (as scope is internally a property of the function), then if you wish to use a different scope, you must manually create a new closure with the desired scope prior to application. However, closures that are scoped must currently either be static (cannot be bound or used with ->call) or bound. This would mean that you would have to bind the closure to some dummy object in order to produce a closure with the desired scope for use with ->call. This is inconvenient at best. To solve this, we relax the current invariant of scoped closures having to be bound, and add a new parameter to bind(To) to produce an unbound, scoped closure, like so:

class Foo { private $x = 3; }
$foo = new Foo;
$foobar = function () { var_dump($this->bar); };
// without the last parameter (optional, defaults to false), we'd get a static, not unbound closure
$foobar = $foobar->bindTo(null, 'FooBar', true);
$foobar->call($foo); // prints int(3)

My Function Referencing as Closures proposal similarly relaxes the requirement for a scoped closure to be bound or static out of necessity, and ->call would be highly useful to that proposal, so this RFC can be considered a prerequisite to it.

Performance

While not the sole benefit of this RFC, it can provide a significant performance impact:

andreas-air:php-src ajf$ time sapi/cli/php -r '\
$a = function () { return $this->x; };\
class FooBar { private $x = 3; }\
$foobar = new FooBar;\
for ($i = 0; $i < 100000; $i++) {\
    $x = $a->bindTo($foobar, "FooBar");\
    $x();\
}'
 
real 0m0.259s
user 0m0.208s
sys 0m0.012s
 
andreas-air:php-src ajf$ time sapi/cli/php -r '\
$a = function () { return $this->x; };\
$a = $a->bindTo(NULL, "FooBar", true);\
class FooBar { private $x = 3; }\
$foobar = new FooBar;\
for ($i = 0; $i < 100000; $i++) {\
    $a->call($foobar);\
}'
 
real 0m0.100s
user 0m0.094s
sys 0m0.005s

If we up the iterations by 10x, the result is the same:

andreas-air:php-src ajf$ time sapi/cli/php -r '\
$a = function () { return $this->x; };\
class FooBar { private $x = 3; }\
$foobar = new FooBar;\
for ($i = 0; $i < 1000000; $i++) {\
    $x = $a->bindTo($foobar, "FooBar");\
    $x();\
}'
 
real 0m1.966s
user 0m1.959s
sys 0m0.005s
 
andreas-air:php-src ajf$ time sapi/cli/php -r '\
$a = function () { return $this->x; };\
$a = $a->bindTo(NULL, "FooBar", true);\
class FooBar { private $x = 3; }\
$foobar = new FooBar;\
for ($i = 0; $i < 1000000; $i++) {\
    $a->call($foobar);\
}'
 
real 0m0.962s
user 0m0.897s
sys 0m0.015s

Backward Incompatible Changes and RFC Impact

This has no effect on backwards compatibility.

Proposed PHP Version(s)

This is proposed for the next version of PHP, either the next 5.x or PHP NEXT, whichever comes sooner. The patch is based on master, intended for the next 5.x.

Future Scope

Partial application (where a new closure is returned that pre-fills the first X arguments) is a possibly worthwhile (though more difficult to implement) addition.

Proposed Voting Choices

This is not a language change, so a straight 50%+1 Yes/No vote can be held.

Patches and Tests

A branch which implements this (with a test) based on the current master can be found here: https://github.com/TazeTSchnitzel/php-src/tree/Closure_apply

References

Changelog

  • v0.2.1 - Added performance section
  • v0.2 - Closure::apply renamed to Closure::call for consistency with JavaScript (former takes an array in JS à la call_user_func_array, latter bare parameters)
  • v0.1 - Initial version
rfc/closure_apply.1407331768.txt.gz · Last modified: 2017/09/22 13:28 (external edit)