rfc:allow_casting_closures_into_single-method_interface_implementations

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:allow_casting_closures_into_single-method_interface_implementations [2023/04/25 09:48] nicolasgrekasrfc:allow_casting_closures_into_single-method_interface_implementations [2023/04/25 19:34] nicolasgrekas
Line 1: Line 1:
-===== Title: Allow casting closures into single-method interface implementations =====+====== PHP RFC: Allow casting closures into single-method interface implementations ======
  
   * Version: 1.0   * Version: 1.0
Line 8: Line 8:
   * Implementation: TBD   * Implementation: TBD
  
-==== Introduction ====+===== Introduction =====
  
 This RFC proposes a new method for the <php>Closure</php> class called <php>castTo()</php>. This method would allow developers to create an instance of a class that implements a specified interface and uses the closure as the implementation. This RFC proposes a new method for the <php>Closure</php> class called <php>castTo()</php>. This method would allow developers to create an instance of a class that implements a specified interface and uses the closure as the implementation.
  
-==== Proposal ====+===== Proposal =====
  
 A new method, <php>castTo()</php>, would be added to the <php>Closure</php> class with the following signature (generics added for extra clarity): A new method, <php>castTo()</php>, would be added to the <php>Closure</php> class with the following signature (generics added for extra clarity):
Line 61: Line 61:
   * **Providing a type-safe alternative to the <php>Closure</php> type**: instead of typing against the <php>Closure</php> type, developers could declare and use interfaces that have a single <php>__invoke()</php> method. While this is possible already, this is quite rare in practice because it comes at a high syntactic cost for end users, which are then required to define a full class. By allowing to quickly turn a closure into an implementation of such interfaces, the <php>castTo()</php> method would greatly reduce this boilerplate, leading to a wider adoption of type-safe code for closures.   * **Providing a type-safe alternative to the <php>Closure</php> type**: instead of typing against the <php>Closure</php> type, developers could declare and use interfaces that have a single <php>__invoke()</php> method. While this is possible already, this is quite rare in practice because it comes at a high syntactic cost for end users, which are then required to define a full class. By allowing to quickly turn a closure into an implementation of such interfaces, the <php>castTo()</php> method would greatly reduce this boilerplate, leading to a wider adoption of type-safe code for closures.
  
-=== Example 1: Using a closure as a default implementation for a TranslatorInterface ===+==== Example 1: Using a closure as a default implementation for a TranslatorInterface ====
  
 This example demonstrates how to use the <php>castTo()</php> method to create a default implementation for a ''TranslatorInterface'' using a closure. It combines this proposal with the first-class callable syntax. This example demonstrates how to use the <php>castTo()</php> method to create a default implementation for a ''TranslatorInterface'' using a closure. It combines this proposal with the first-class callable syntax.
Line 85: Line 85:
 If no custom implementation of ''TranslatorInterface'' is provided during the instantiation of ''SomeClass'', a default implementation is  derived using a closure created from the <php>strtr()</php> function. If no custom implementation of ''TranslatorInterface'' is provided during the instantiation of ''SomeClass'', a default implementation is  derived using a closure created from the <php>strtr()</php> function.
  
-=== Example 2: Type-safe alternative to using Closure ===+==== Example 2: Type-safe alternative to using Closure ====
  
-Imagine that you have the following function, that takes a closure and two number to do some operation on them:+Imagine that you have the following function, that takes a closure and two numbers to do some operation on them:
  
 <code php> <code php>
-function executeOperation(Closure $operation, int $a, int $b): int { +function executeOperation(Closure $operator, int $a, int $b): int { 
-    return $operation($a, $b);+    return $operator($a, $b);
 } }
 </code> </code>
Line 102: Line 102:
 } }
  
-function executeOperation(InvokableInterface $operation, int $a, int $b): int { +function executeOperation(OperatorInterface $operator, int $a, int $b): int { 
-    return $operation($a, $b);+    return $operator($a, $b);
 } }
  
Line 126: Line 126:
 In this example, an ''OperatorInterface'' is defined with a single <php>__invoke()</php> method, which can be used to replace the <php>Closure</php> type on the <php>executeOperation()</php> function. The <php>castTo()</php> method is then used to create instances of the interface, with different behaviors implemented by closures. This approach provides a more type-safe and expressive way to handle closures while adhering to the required interface. In this example, an ''OperatorInterface'' is defined with a single <php>__invoke()</php> method, which can be used to replace the <php>Closure</php> type on the <php>executeOperation()</php> function. The <php>castTo()</php> method is then used to create instances of the interface, with different behaviors implemented by closures. This approach provides a more type-safe and expressive way to handle closures while adhering to the required interface.
  
-=== Example 3: Decoupling URI template implementations using adapters ===+==== Example 3: Decoupling URI template implementations using adapters ====
  
 This example demonstrates how the <php>castTo()</php> method can be used to create adapters for This example demonstrates how the <php>castTo()</php> method can be used to create adapters for
Line 167: Line 167:
 </code> </code>
  
-In this example, an ''UriExpanderInterface''' is defined with an <php>expand()</php> method.+In this example, an ''UriExpanderInterface'' is defined with an <php>expand()</php> method. 
 3 different implementations of the interface are derived using the <php>castTo()</php> method. 3 different implementations of the interface are derived using the <php>castTo()</php> method.
  
Line 176: Line 177:
 illustrating the decoupling and flexibility provided by using adapters created with the <php>castTo()</php> method. illustrating the decoupling and flexibility provided by using adapters created with the <php>castTo()</php> method.
  
-==== Backward Incompatible Changes ====+==== Prototype implementation ==== 
 + 
 +Using <php>eval()</php> and anonymous classes, it's already possible to create a function that turns a closure into an implementation of a single-method interface. Conceptually, this RFC could be coded this way: 
 + 
 +<code php> 
 +class Closure 
 +
 +    // [...] Closure is an internal class 
 +     
 +    public function castTo(self $closure, string $interface): object 
 +    { 
 +        static $cache = []; 
 + 
 +        $cacheKey = <compute-key-from-$closure-and-$interface> 
 + 
 +        if (isset($cache[$cacheKey])) { 
 +            return $cache[$cacheKey]($closure); 
 +        } 
 +         
 +        $method = <name-of-the-method-in-$interface>; 
 +        $signature = <signature-of-$closure>; 
 +        $return = str_ends_with($signature, '): never') || str_ends_with($signature, '): void') ? '' : 'return '; 
 + 
 +        $object = eval(<<<PHP 
 +            return new class (\$closure) implements {$interface} { 
 +                public function __construct(private readonly Closure \$closure) { 
 +                } 
 +                public function {$method}{$signature} { 
 +                    {$return}\$this->closure->__invoke(<list-of-arguments-in-$closure>); 
 +                } 
 +            }; 
 +            PHP); 
 + 
 +        $cache[$cacheKey] = $object::class; 
 + 
 +        return $object; 
 +    } 
 +
 +</code> 
 + 
 +A working prototype implementation is provided by this package: 
 +https://github.com/tchwork/closure-caster/blob/main/src/function.php 
 + 
 +===== Backward Incompatible Changes =====
  
 This proposal introduces no backward incompatible changes, as it only adds a new method to the <php>Closure</php> class. This proposal introduces no backward incompatible changes, as it only adds a new method to the <php>Closure</php> class.
  
-==== Open Issues ====+===== Open Issues =====
  
   * exact behavior when checks fail   * exact behavior when checks fail
Line 186: Line 230:
   * would the engine allow implementing this without adding any extra frame in the call stack?   * would the engine allow implementing this without adding any extra frame in the call stack?
  
-==== Unaffected PHP Functionality ====+===== Unaffected PHP Functionality =====
  
 No existing PHP functionality would be affected by this proposal. No existing PHP functionality would be affected by this proposal.
  
-==== Future Scope ====+===== Future Scope =====
  
   * Allow a closure to declare a single-method interface it implements, as in e.g. <php>function () implements FooInterface {...}</php>. See https://wiki.php.net/rfc/allow-closures-to-declare-interfaces-they-implement   * Allow a closure to declare a single-method interface it implements, as in e.g. <php>function () implements FooInterface {...}</php>. See https://wiki.php.net/rfc/allow-closures-to-declare-interfaces-they-implement
Line 197: Line 241:
 Both ideas are complementary to the one proposed in this RFC. Both ideas are complementary to the one proposed in this RFC.
  
-==== Proposed PHP Version ====+===== Proposed PHP Version =====
  
 This RFC targets PHP version 8.3. This RFC targets PHP version 8.3.
  
-==== Vote ====+===== Vote =====
  
 The vote will require a 2/3 majority to be accepted. Voting will start on [Vote start date] and end on [Vote end date]. The vote will require a 2/3 majority to be accepted. Voting will start on [Vote start date] and end on [Vote end date].
  
  
rfc/allow_casting_closures_into_single-method_interface_implementations.txt · Last modified: 2023/04/25 19:36 by nicolasgrekas