rfc:closures_in_const_expr

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:closures_in_const_expr [2024/10/25 09:32] timwollarfc:closures_in_const_expr [2024/11/13 13:35] (current) – Voting timwolla
Line 2: Line 2:
   * Version: 1.0   * Version: 1.0
   * Date: 2024-10-24   * Date: 2024-10-24
-  * Author: Tim Düsterhustim@tideways-gmbh.com +  * Author: Tim Düsterhus (tim@tideways-gmbh.com), Volker Dusch (volker@tideways-gmbh.com) 
-  * Status: Draft+  * Status: In Voting
   * First Published at: https://wiki.php.net/rfc/closures_in_const_expr   * First Published at: https://wiki.php.net/rfc/closures_in_const_expr
  
 ===== Introduction ===== ===== Introduction =====
  
-Several PHP constructs are limited to accept “constant expressions” only. These expressions may only contain a limited number of operations that can roughly be summarized as “unchangeable values”. Notably attribute parameters only accept constant expressions and Closures are not currently part of the set of allowed operations in constant expressions.+Several PHP constructs are limited to accept “constant expressions” only. These expressions may only contain a limited number of operations that can roughly be summarized as “immutable values”. Notably attribute parameters are a construct that only accepts constant expressions and Closures are not currently part of the set of allowed operations in constant expressions. 
 + 
 +As Closures are effectively just PHP source code (or rather: PHP Opcodes) they are an immutable value (when limiting some of the features) and as such there is no fundamental reason why they should not be allowed within constant expressions. And indeed there are some use cases that would be enabled by allowing Closures to appear in constant expressions. 
 + 
 +As an example, it would enable a userland definition of <php>array_filter()</php> with the default filter callback that checks for emptiness, without needing to make the callback parameter nullable: 
 + 
 +<PHP> 
 +<?php 
 + 
 +function my_array_filter( 
 +    array $array, 
 +    Closure $callback = static function ($item) { return !empty($item); }, 
 +) { 
 +    $result = []; 
 + 
 +    foreach ($array as $item) { 
 +        if ($callback($item)) { 
 +            $result[] = $item; 
 +        } 
 +    } 
 + 
 +    return $result; 
 +
 + 
 +var_dump(my_array_filter([ 
 +    0, 1, 2, 
 +    '', 'foo', 'bar', 
 +])); 
 + 
 +?> 
 +</PHP>
  
 ===== Proposal ===== ===== Proposal =====
  
-It shall be legal to include Closures in constant expressions. This includes:+This RFC proposes that it shall be legal to include Closures in constant expressions. This includes:
  
   * Attribute parameters.   * Attribute parameters.
-  * Default values of properties, parameters, and static variables.+  * Default values of properties and parameters.
   * Constants and Class Constants.   * Constants and Class Constants.
  
 ==== Constraints ==== ==== Constraints ====
  
-Closures placed in constant expressions are subject to the following constraints:+If Closures are placed in constant expressions they are subject to the following constraints:
  
-  * They must not include variables from the surrounding scope using <php>use($foo, $bar)</php>, because except for constants and static variables there is no surrounding scope. This also means that short closures (arrow functions) are not supported, because they perform implicit capturing. This constraint is consistent with how variables may not be part of a constant expression. +  * They may not include variables from the surrounding scope using <php>use($foo, $bar)</php>, because except for constants and possibly for default parameter values there is no surrounding scope. This also means that short closures (arrow functions) are not supported, because they perform implicit capturing. This constraint is consistent with how variables may not be part of a constant expression. 
-  * They must be <php>static</php> (and thus they must not access <php>$this</php>). Semantically <php>$this</php> would only be well-defined for property default values and possibly attribute parameters, but this would require reevaluating the Closure for each object / attribute instance, which would be different to existing constant expressions which are only evaluated once. This constraint is consistent with how <php>new</php> expressions may not be used in property default values.+  * They must be <php>static</php> (and thus they must not access <php>$this</php>). Semantically <php>$this</php> would only be well-defined for property default values and possibly attribute parameters, but this would require reevaluating the Closure for each object / attribute instance, which would be different to existing constant expressions which are only evaluated once. This constraint is consistent with how <php>new</php>-expressions may not be used in property default values
 + 
 +Both of these constraints will be verified at compile time.
  
 ==== Scoping ==== ==== Scoping ====
Line 29: Line 61:
 As with other constant-expressions, Closures defined in constant expressions follow the expected scoping rules of the context they are placed in. This means that Closures in property default values //may// access <php>private</php> properties, methods, and class constants of the class where they are defined, similarly to how a Closure defined in the constructor and stored in a property may access those private members. Likewise are Closures in attribute parameters allowed to access private members of the surrounding class. As with other constant-expressions, Closures defined in constant expressions follow the expected scoping rules of the context they are placed in. This means that Closures in property default values //may// access <php>private</php> properties, methods, and class constants of the class where they are defined, similarly to how a Closure defined in the constructor and stored in a property may access those private members. Likewise are Closures in attribute parameters allowed to access private members of the surrounding class.
  
-==== Use Cases ====+==== Closures in sub-expressions ==== 
 + 
 +Closures behave like any other operation within a constant expression, thus they may be part of a sub-expression of another operation. While it is not particularly useful to use Closures as an operand to mathematical expressions, it will also not break anything. 
 + 
 +However the following operations are examples of how Closures can usefully be included in sub-expressions. 
 + 
 +Defining a list of Closures in a default parameter: 
 +<PHP> 
 +<?php 
 + 
 +function foo( 
 +    string $input, 
 +    array $callbacks = [ 
 +        static function ($value) { 
 +            return \strtoupper($value); 
 +        }, 
 +        static function ($value) { 
 +            return \preg_replace('/[^A-Z]/', '', $value); 
 +        }, 
 +    ] 
 +) { 
 +    foreach ($callbacks as $callback) { 
 +        $input = $callback($input); 
 +    } 
 + 
 +    return $input; 
 +
 + 
 +var_dump(foo('Hello, World!')); // string(10) "HELLOWORLD" 
 +</PHP> 
 + 
 +Passing a Closure to a <php>new</php>-expression: 
 + 
 +<PHP> 
 +<?php 
 + 
 +class MyObject 
 +
 +    public function __construct(private Closure $callback) {} 
 +
 + 
 +const Foo = new MyObject(static function () { 
 +    return 'foo'; 
 +}); 
 +</PHP> 
 + 
 +===== Use Cases =====
  
 Custom field validation for an attribute-based object validation library: Custom field validation for an attribute-based object validation library:
Line 55: Line 133:
         }         }
     })]     })]
-    public function testSubtraction(int $minuend, float $subtrahend, int $result)+    public function testSubtraction(int $minuend, int $subtrahend, int $result)
     {     {
         \assert(Calculator::subtract($minuend, $subtrahend) === $result);         \assert(Calculator::subtract($minuend, $subtrahend) === $result);
Line 62: Line 140:
 </PHP> </PHP>
  
 +Custom formatting for an attribute-based serialization library:
 +
 +<PHP>
 +final class LogEntry
 +{
 +     public string $message;
 +     
 +     #[Serialize\Custom(static function (string $severity): string {
 +         return \strtoupper($severity);
 +     })]
 +     public string $severity;
 +}
 +</PHP>
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
Line 107: Line 198:
   * Support non-static Closures.   * Support non-static Closures.
   * Support first-class callables.   * Support first-class callables.
 +  * Support variable capturing if/when variables may appear in constant expressions.
  
 ===== Proposed Voting Choices ===== ===== Proposed Voting Choices =====
  
-<doodle title="Support Closures in constant expressions as proposed?" auth="timwolla" voteType="single" closed="true" closeon="2024-06-05T08:00:00Z">+<doodle title="Support Closures in constant expressions as proposed?" auth="timwolla" voteType="single" closed="false" closeon="2024-11-27T14:00:00Z">
    * Yes    * Yes
    * No    * No
rfc/closures_in_const_expr.1729848777.txt.gz · Last modified: 2024/10/25 09:32 by timwolla