Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision |
rfc:closure_apply [2014/07/29 23:18] – Clarity, tests (branch not tree) ajf | rfc:closure_apply [2014/08/17 19:24] – ajf |
---|
====== PHP RFC: Closure::apply ====== | ====== PHP RFC: Closure::call ====== |
* Version: 0.1 | * Version: 0.2.1 |
* Date: 2014-07-29 | * Date: 2014-07-29, put to internals 2014-08-03, latest 2014-08-06 |
* Author: Andrea Faulds, ajf@ajf.me | * Author: Andrea Faulds, ajf@ajf.me |
* Status: Draft | * Status: Under Discussion |
* First Published at: http://wiki.php.net/rfc/closure_apply | * First Published at: http://wiki.php.net/rfc/closure_apply |
| |
===== Introduction ===== | ===== 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 without creating a new closure, making binding to different objects and calling for multiple objects cumbersome and inefficient (at least two statements are needed, and a new closure must be created and immediately disposed of for each). | 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 ===== | ===== Proposal ===== |
| |
<code php> | <code php> |
mixed Closure::apply(object $to[, mixed ...$parameters]) | mixed Closure::call(object $to[, mixed ...$parameters]) |
</code> | </code> |
| |
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 (calling ''->apply'' will fail). | 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: | It can be used like so: |
$foo->bar = 3; | $foo->bar = 3; |
$foobar = function ($qux) { var_dump($this->bar + $qux); }; | $foobar = function ($qux) { var_dump($this->bar + $qux); }; |
$foobar->apply($foo, 4); // prints int(7) | $foobar->call($foo, 4); // prints int(7) |
</code> | </code> |
| |
Because the ''->apply'' 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 ''->apply'') 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 ''->apply''. 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: | 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: |
| |
<code php> | <code php> |
// without the last parameter (optional, defaults to false), we'd get a static, not unbound closure | // without the last parameter (optional, defaults to false), we'd get a static, not unbound closure |
$foobar = $foobar->bindTo(null, 'FooBar', true); | $foobar = $foobar->bindTo(null, 'FooBar', true); |
$foobar->apply($foo); // prints int(3) | $foobar->call($foo); // prints int(3) |
</code> | </code> |
| |
My function referencing as closures proposal similarly relaxes the requirement for a scoped closure to be bound or static out of necessity, and ''->apply'' would be highly useful to that proposal, so this RFC can be considered a prerequisite to it. | My [[rfc:function_referencing|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. |
| |
| ''call'' would be useful in many cases where ''bindTo'' is used (e.g. [[https://github.com/search?l=php&p=34&q=bindTo&ref=cmdform&type=Code|search of GitHub for bindTo]]). A search on GitHub reveals [[https://github.com/search?q=bindTo+call_user_func&type=Code&ref=searchresults|many using bindTo and immediately calling with call_user_func]], which would now not be necessary as they could just use ''call''. |
| |
| ===== Performance ===== |
| |
| While not the sole benefit of this RFC, it can provide a performance improvement in some applications. |
| |
| We use two test scripts, ''a.php'' using bindTo and ''b.php'' using call. |
| |
| <file php a.php> |
| $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(); |
| } |
| </file> |
| |
| <file php b.php> |
| $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); |
| } |
| </file> |
| |
| When run for 100000 iterations, ''b.php'' shows a 2x improvement over ''a.php'': |
| |
| <code> |
| andreas-air:php-src ajf$ time sapi/cli/php a.php |
| |
| real 0m0.259s |
| user 0m0.208s |
| sys 0m0.012s |
| |
| andreas-air:php-src ajf$ time sapi/cli/php b.php |
| |
| real 0m0.100s |
| user 0m0.094s |
| sys 0m0.005s |
| </code> |
| |
| If we up the iterations by 10x, the result is the same: |
| |
| <code> |
| andreas-air:php-src ajf$ time sapi/cli/php a.php |
| |
| real 0m1.966s |
| user 0m1.959s |
| sys 0m0.005s |
| |
| andreas-air:php-src ajf$ time sapi/cli/php b.php |
| |
| real 0m0.962s |
| user 0m0.897s |
| sys 0m0.015s |
| </code> |
| |
===== Backward Incompatible Changes and RFC Impact ===== | ===== Backward Incompatible Changes and RFC Impact ===== |
===== Patches and Tests ===== | ===== 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/function_reference | 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 |
| |
| There is a pull request for review purposes here: https://github.com/php/php-src/pull/775 |
| |
===== References ===== | ===== References ===== |
| |
* As aforementioned, my [[rfc:function_referencing|Function Referencing as Closures]] RFC has this RFC as a prerequisite | * As previously mentioned, my [[rfc:function_referencing|Function Referencing as Closures]] RFC has this RFC as a prerequisite |
| |
| ===== 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 |