rfc:closurefromcallable

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:closurefromcallable [2015/09/28 15:33] danackrfc:closurefromcallable [2017/09/22 13:28] (current) – external edit 127.0.0.1
Line 1: Line 1:
 ====== PHP RFC: Closure from callable function ====== ====== PHP RFC: Closure from callable function ======
-  * Version: 0.9 +  * Version: 1 
-  * Date: 2015-09-28 +  * Date: 2016-04-23 
   * Author: Dan Ackroyd, Danack@php.net   * Author: Dan Ackroyd, Danack@php.net
-  * Status: Draft +  * Status: Implemented (PHP 7.1)
   * First Published at: https://wiki.php.net/rfc/closurefromcallable   * First Published at: https://wiki.php.net/rfc/closurefromcallable
  
Line 11: Line 11:
 PHP lacks a simple function to convert callables into a closure directly. Although it is possible to do this in userland, it is slow compared to what is possible with a built-in function, due to the number of reflection functions needed to be called, as well needing to create intermediary reflection objects. PHP lacks a simple function to convert callables into a closure directly. Although it is possible to do this in userland, it is slow compared to what is possible with a built-in function, due to the number of reflection functions needed to be called, as well needing to create intermediary reflection objects.
  
-This RFC proposes adding the function `closure` to PHP's Reflection extension, to allow people to convert callables into closures directly. The method signature would be:+This RFC proposes adding a static method to the Closure class, to allow conversion of callables into closures directly. The method signature would be:
  
 <code php> <code php>
-function closure(callable $callable) : closure {...}+class Closure { 
 +    ... 
 +    public static function fromCallable(callable $callable) : Closure {...
 +    ... 
 +}
 </code> </code>
  
-The function will check whether the callable is actually callable in the current scope. It will return a closure if it is callable, otherwise throw an Error. For example trying to create a closure to a private method of an object from the global scope would fail. Trying to create a closure to a private method of an object from within the object would succeed.+The function will check whether the callable is actually callable in the current scope. It will return a closure if it is callable, otherwise throw a TypeError. For example trying to create a closure to a private method of an object from the global scope would fail. Trying to create a closure to a private method of an object from within the object would succeed.
  
  
Line 24: Line 28:
 There are three uses for converting callables into closures: There are three uses for converting callables into closures:
  
-* Better API control for classes+  * Better API control for classes
  
-* Easier error detection and static analysis+  * Easier error detection and static analysis
  
-* Performance+  * Performance
  
  
Line 47: Line 51:
     }     }
          
-    public function emailValidation() {...}+    public function emailValidation($userData) {...}
          
-    public function genericValidation() {...}+    public function genericValidation($userData) {...}
 } }
  
 $validator = new Validator(); $validator = new Validator();
-$callback = $validator->getValidator('email');+$callback = $validator-> getValidatorCallback('email');
 $callback($userData); $callback($userData);
 </code> </code>
  
-With the closure() function, the class can easily return a closure that closes over private functions, which means those functions are no longer part of the public API.+With the Closure::fromCallable() method, the class can easily return a closure that closes over private functions, which means those functions are no longer part of the public API.
  
 <code php> <code php>
Line 65: Line 69:
          
         if ($validationType == 'email') {         if ($validationType == 'email') {
-            return closure([$this, 'emailValidation']);+            return Closure::fromCallable([$this, 'emailValidation']);
         }         }
                  
-        return closure([$this, 'genericValidation']);+        return Closure::fromCallable([$this, 'genericValidation']);
     }     }
-     + 
-    private function emailValidation() {...} +    private function emailValidation($userData) {...} 
-     + 
-    private function genericValidation() {...}+    private function genericValidation($userData) {...}
 } }
  
 $validator = new Validator(); $validator = new Validator();
-$callback = $validator->getValidator('email');+$callback = $validator->getValidatorCallback('email');
 $callback($userData); $callback($userData);
  
Line 98: Line 102:
 // more code here. // more code here.
 //... //...
-$callback(); //error happens here - Fatal error: Call to undefined function food()+$callback(); //error happens here
 </code> </code>
  
Line 107: Line 111:
  
 function getCallback() { function getCallback() {
-    return closure('food'); //error happens here - Fatal error: Call to undefined function food()+    return Closure::fromCallable('food'); //error happens here
 } }
  
Line 121: Line 125:
 ==== Performance gain ==== ==== Performance gain ====
  
-Although PHP has a 'callable' type it is quite slow to use compared to other types. This is due to the amount of work that is needed to check whether a 'callable' is actually a valid callable or not. This work needs to be each time the 'callable' parameter is called. +Although PHP has a 'callable' type it is quite slow to use compared to other types. This is due to the amount of work that is needed to check whether a 'callable' is actually a valid callable or not. This work needs to be performed each time the 'callable' parameter is called. 
-  +
- +
 Below are two files that call a function 10,000 times which calls itself recursively 8 times. One version has a callable type for the parameter, the other has Closure as the type. Measuring the number of operations with Below are two files that call a function 10,000 times which calls itself recursively 8 times. One version has a callable type for the parameter, the other has Closure as the type. Measuring the number of operations with
    
Line 132: Line 135:
  
 ^        ^ Operations  ^        ^ Operations 
-| Callable | 112,311,913 |+| Callable | 114,465,014 |
 | Closure | 95,522,330 |     | Closure | 95,522,330 |    
-| Difference | 16,789,583 | +| Difference | 18,942,684 |
- +
-Which is a difference of over 1000 operations per loop.+
  
 +Which is saving 16% of the operations, or a difference of 1,894 operations per loop or about 230 operations per function call where the callable is passed..
  
  
 ==== Patches and Tests ==== ==== Patches and Tests ====
  
-The function has been implemented in this branch: https://github.com/Danack/php-src/tree/to_closure The patch is not finished as the error messages need some improvement including the exact Error/Exception that should be thrown.+The function has been implemented in this PR: https://github.com/php/php-src/pull/1906
    
    
 ==== Proposed Voting Choices ==== ==== Proposed Voting Choices ====
-As this is a simple function addition with no langauages changes, the voting will requie a 50%+1 majority to include this in PHP 7.1+As this is a simple function addition with no language changes, the voting will require a 50%+1 majority to include this in PHP 7.1 
 + 
 + 
 +Voting will close at 9pm UTC on the 29th of May 2016. 
 +<doodle title="Accept Closure from Callble? (50%+1 majority required)" auth="Danack" voteType="single" closed="true"> 
 +   * Yes 
 +   * No 
 +</doodle>
    
    
-==== Why a function? ====+==== Why the long name? ====
  
-Some people have asked why a function is appropriate rather than making it be a constructor of the Closure class. e.g. `$fn = new Closure($callable);`. Using a plain function allows more flexibility in the implementation, without having anything be inconsistent. +Although I would prefer to implement this as short function e.g.
- +
-e.g. in cases where the same function is turned into a closure twice:+
  
 <code php> <code php>
-$fn1 = closure('foo'); +    function fn(callable $callable: Closure {}
-$fn2 = closure('foo');+
 </code> </code>
  
-with the implementation being function it is perfectly acceptable for $fn1 and $fn2 to be the same objectIf instead the implementation was as a constructor for the closure class:+there are downsides that make that be poor choice.
  
-<code php> +It clutters up the root namespace, which in general is bad practice. Making the implementation be a static method of the Closure class avoids this problem, as well as avoid clashing with future needs. For example Hack has specific separate implementations for converting strings, instance methods to closures. We may wish to add these in the future. Using a static method with a descriptive name avoids clashing with those names in the future.
-$fn1 = new closure('foo'); +
-$fn2 = new closure('foo'); +
-</code>+
  
-Having two new statements return the same object is very high on the surprise factor.+If anyone wants to use a short name for this functionality in their application or library they can do so easily, by defining a function:
  
-Additionally having it be just a function, would make it easier in the future to implement it as a language construct, which might be desired for performance reasons. +<code php> 
- +    function fn(callable $callable) : Closure { 
-If you think that this should be implemented as the constructor of Closure rather than a function, please can you articulate the reason for that, other than 'constructors are cool'.+        return Closure::fromCallable($callable); 
 +    } 
 +</code>
  
  
Line 178: Line 183:
 Hack has a similar functionality, but they have chosen to split the functionality into separate functions for each of the cases. Hack has a similar functionality, but they have chosen to split the functionality into separate functions for each of the cases.
  
-* Strings which should be a function:  http://docs.hhvm.com/manual/en/function.hack.fun.php +  * Strings which should be a function:  http://docs.hhvm.com/manual/en/function.hack.fun.php 
-* Instance and method name: http://docs.hhvm.com/manual/en/function.hack.inst_meth.php +  * Instance and method name: http://docs.hhvm.com/manual/en/function.hack.inst_meth.php 
-* Class name and method name: http://docs.hhvm.com/manual/en/function.hack.class_meth.php+  * Class name and method name: http://docs.hhvm.com/manual/en/function.hack.class_meth.php
  
 They have included these to allow programs to be easier to reason about, and allows their type-checker to statically analyze the hack programs. They have included these to allow programs to be easier to reason about, and allows their type-checker to statically analyze the hack programs.
Line 186: Line 191:
 However this RFC takes the position that it is inappropriate to have a separate function per type. Instead having a single function that takes any callable parameter is more powerful, and easier for users to use. However this RFC takes the position that it is inappropriate to have a separate function per type. Instead having a single function that takes any callable parameter is more powerful, and easier for users to use.
    
 +===== Implementation =====
 +
 +Merged into php-src for PHP 7.1: https://github.com/php/php-src/commit/63ca65d
 +
 +After the project is implemented, this section should contain 
 +  - a link to the PHP manual entry for the feature
 +
 ===== Appendix ===== ===== Appendix =====
    
Line 244: Line 256:
 function getCallable($foo) : Closure function getCallable($foo) : Closure
 { {
-    return closure([$foo, 'bar']);+    return Closure::fromCallable([$foo, 'bar']);
 } }
  
Line 258: Line 270:
  
 </code> </code>
- 
- 
  
rfc/closurefromcallable.1443454397.txt.gz · Last modified: 2017/09/22 13:28 (external edit)