rfc:closure_self_reference

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
Last revisionBoth sides next revision
rfc:closure_self_reference [2020/11/10 21:12] – FIx code blocks crellrfc:closure_self_reference [2023/06/03 21:00] – typo. danack
Line 1: Line 1:
 ====== PHP RFC: Closure self reference ====== ====== PHP RFC: Closure self reference ======
-  * Version: 0.+  * Version: 1.0 
-  * Date: 2020-11-10 +  * Date: 2023-06-03 
-  * Author: Danack +  * Author: Danack, KapitanOczywisty 
-  * Status: Draft +  * Status: Under Discussion
   * First Published at: https://wiki.php.net/rfc/closure_self_reference   * First Published at: https://wiki.php.net/rfc/closure_self_reference
  
 ===== Introduction ===== ===== Introduction =====
  
-Currently, the main way used to call a closure from within that closure, is to bind a variable by reference into the closure, when the closure is created. +Currently, the main way used to call a closure from within that closure, is to bind a variable by reference into the closure, when the closure is created.
  
 However this can lead to shenanigans: However this can lead to shenanigans:
Line 29: Line 29:
  
 i.e. a change to the variable outside the closure has modified the behaviour of the closure. That a closure can change behaviour like this violates the [[https://en.wikipedia.org/wiki/Principle_of_least_astonishment|Principle of least astonishment]] i.e. a change to the variable outside the closure has modified the behaviour of the closure. That a closure can change behaviour like this violates the [[https://en.wikipedia.org/wiki/Principle_of_least_astonishment|Principle of least astonishment]]
 +
 +It would be better if it was possible to reference a closure inside itself, without having to `use` an external variable.
  
 ===== Proposal ===== ===== Proposal =====
  
-Similar to how $this is defined automatically inside closures that are defined inside classes, add $lambda variable to all closures, that references the current closure.+Allow closures to be aliased to a variable that can be used within the closure
 + 
 +<code php> 
 +$fibonacci = function (int $n) as $fn { 
 +    if ($n === 0) return 0; 
 +    if ($n === 1) return 1; 
 +    return $fn($n-1) + $fn($n-2); 
 +}; 
 + 
 +echo $fibonacci(5); 
 +</code> 
 + 
 +Or as a short closure, aka arrow function: 
 + 
 +<code php> 
 +$factorial = fn(int $num) as $fn : int => $num > 1 ? $num * $fn($num - 1) : $num; 
 +</code> 
 + 
 +===== Syntax choices ===== 
 + 
 +There are a plethora of possible syntax choicesThose that have been thought about about are: 
 + 
 +==== A constant ====
  
-A closure that needs to call itself can now use the $lambda variable, instead of having to pass in a variable by reference:+It would be possible to define a constant that refers to the current closure e.g. __CLOSURE__
  
 <code php> <code php>
Line 40: Line 64:
     if ($n === 0) return 0;     if ($n === 0) return 0;
     if ($n === 1) return 1;     if ($n === 1) return 1;
-    return $lambda($n-1) + $lambda($n-2);+    return __CLOSURE__($n-1) + __CLOSURE__($n-2);
 }; };
 +
 +$factorial = fn(int $num): int => $num > 1 ? $num * __CLOSURE__($num - 1) : $num;
 </code> </code>
  
-Any use of $lambda outside of a closure would give an error, similar to how using $this outside of a method of a class instance is not allowed.+ 
 +==== Alias after function return type ====
  
 <code php> <code php>
-function foo() { +$fibonacci = function (int $n): int as $fn 
-   $lambda(); +    if ($n === 0return 0
-+    if ($n === 1return 1
-foo(); +    return $fn($n-1) + $fn($n-2); 
-// Fatal error: Using $lambda when not in closure context in %s+};
 </code> </code>
  
 +Due to parser limitations, this can't be used for short closures.
  
-===== BC concerns ===== 
  
-Although this proposal wouldn't require a large amount of change to existing code, people might not be aware that the use $lambda anywhere in their code.+==== A static function on the closure class ====
  
-Because of this it probably makes sense to break the change into two steps:+e.g. Closure::current()
  
-  * In one release emit a deprecation notice for any use of a variable named 'lambda'. +<code php> 
-  * In the next release add the ability to use $lambda inside a closure, with the meaning of 'the current closure'.+$fibonacci = function (int $n) { 
 +    if ($n === 0) return 0; 
 +    if ($n === 1) return 1; 
 +    return (Closure::current())($n-1) + (Closure::current())($n-2); 
 +};
  
-==== Why the name '$lambda' ====+$factorial fn(int $num) as $fn : int =$num > 1 $num * (Closure::current())($num - 1) : $num; 
 +</code>
  
-Seems a reasonable choice as: 
  
-  * People who don't know what lambda is are unlikely to use that name by accident. + 
-  * People who do know what a lambda is are likely to be able to remember it. +==== De-anonymize the function ==== 
-  * I find it a a fun word to say.+ 
 +i.e. allow variable name to be used after the keyword function before the parentheses containing the parameters:
  
 <code php> <code php>
-$closure = function () { echo "Hello.";}; +$fibonacci = function $fn(int $n) { 
-// Fatal errorUsing $closure when not in closure context in %s +    if ($n === 0) return 0; 
-// Whoever wrote this code is likely to be confused.+    if ($n === 1) return 1; 
 +    return $fn($n-1) + $fn($n-2); 
 +}; 
 + 
 +$factorial = fn $fn(int $num) as $fn int => $num > 1 ? $num * $fn($num - 1) : $num;
 </code> </code>
 +
 +This has a large aesthetic downside of appearing to create the closure variable in the scope that the closure is declared in, rather than internal to the closure scope.
 +
 +
 +==== Alias after use variables ====
  
 <code php> <code php>
-$lambda = function () { echo "Hello.";}+$fibonacci_offset = function (int $nuse ($offset) as $fn: int { 
-// Fatal error: Using $lambda when not in closure context in %s +    if ($n === 0) return $offset; 
-// Whoever wrote this code has a reasonable chance of understanding the issue.+    if ($n === 1) return 1
 +    return $fn($n-1) + $fn($n-2); 
 +};
 </code> </code>
  
-===== Backward Incompatible Changes =====+<code php> 
 +$factorial fn(int $num) as $fn: int => $num > 1 ? $num * $fn($num - 1) : $num; 
 +</code>
  
-Use of any variable named $lambda would issue a deprecation notice on the version where a deprecation notice is added. 
  
-Any use of $lambda would have to be inside a closure for versions after it is added.   
  
-===== Proposed PHP Versions =====+==== Alias immediately after function parameters ==== 
 +<code php> 
 +$fibonacci_offset function (int $n) as $fn use($offset): int { 
 +    if ($n === 0) return $offset; 
 +    if ($n === 1) return 1; 
 +    return $fn($n-1) + $fn($n-2); 
 +}; 
 +</code>
  
-The voting choices will allow people to choose between:+<code php> 
 +$factorial = fn(int $num) as $fn int => $num > 1 ? $num * $fn($num - 1) : $num; 
 +</code>
  
-Either: Deprecate use of $lambda in 8.1 and introduce $lambda to be available inside closures for 8.2 
  
-Or: Deprecate use of $lambda in the last planned minor release of PHP 8 and introduce $lambda to be available inside closures for PHP 9.0+===== Syntax choice evaluation =====
  
-or rejecting the RFC.+Of the syntaxes considered, the following syntaxes are excluded for the reasons listed: 
 + 
 +* A constant. Although this could work it has multiple aesthetic downsides of being verbose, ugly, and just not very language-y. 
 + 
 +* Alias after function return type. Can't be used for short closures due to parser limitations. 
 + 
 +* A static function on the closure class. Just not very language-y, quite verbose due to the extra ()'s needed 
 + 
 +* De-anonymize the function. This has a large aesthetic downside of appearing to create the closure variable in the scope that the closure is declared in, rather than internal to the closure scope. 
 + 
 +Which leaves the 'Alias after use variables' and 'Alias immediately after function parameters' as the acceptable options. 
 + 
 +As the authors find the 'Alias immediately after function parameters' easiest to read, that is the syntax that has been chosen. 
 + 
 +===== Backward Incompatible Changes ===== 
 + 
 +None known. 
 + 
 +===== Proposed PHP Versions ===== 
 + 
 +8.3
  
 ===== RFC Impact ===== ===== RFC Impact =====
Line 107: Line 178:
 ===== Future Scope ===== ===== Future Scope =====
  
-==== Why not make $lambda usable in all functions, and just refer to the current function/method ==== +===== Questions =====
- +
-Probably better to just error...unless someone can think of good reasons either way. TBH, I'd probably prefer it to error, to prevent accidental usage.+
  
 ===== Proposed Voting Choices ===== ===== Proposed Voting Choices =====
  
-==== Vote 1 ==== +Accept this RFC and make it possible to reference a closure from within itself using `as $variable` ? Yes/no.
- +
-  * Accept this RFC, give deprecation notice for any use of a variable called 'lambda' in PHP 8.1 and support the $lambda variable inside any closure from PHP 8.2 +
-  * Reject this RFC. +
- +
-==== Vote 2 ==== +
-  * Accept this RFC, but at versions PHP 8.x (where x is the last planned minor version for PHP 8) for deprecation and adding $lambda to PHP 9.0. +
-  * Reject this RFC. +
- +
-If the result of 'vote 1' is to accept the RFC, then vote 2 is moot.+
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
Line 128: Line 188:
  
 ===== Implementation ===== ===== Implementation =====
-None yet.+https://github.com/php/php-src/pull/11118
  
 ===== References ===== ===== References =====
rfc/closure_self_reference.txt · Last modified: 2023/06/03 21:01 by danack