rfc:callable-types

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:callable-types [2016/04/21 23:08] – fix formatting error marciorfc:callable-types [2017/09/22 13:28] (current) – external edit 127.0.0.1
Line 1: Line 1:
-====== PHP RFC: Callable Types ====== +====== PHP RFC: Callable Prototypes ====== 
-  * Version: 0.0+  * Version: 1.0
   * Date: 2015-08-27   * Date: 2015-08-27
   * Authors: Nikita Nefedov <inefedor@gmail.com>, Márcio Almada <marcio3w@gmail.com>   * Authors: Nikita Nefedov <inefedor@gmail.com>, Márcio Almada <marcio3w@gmail.com>
-  * Status: Draft+  * Status: Declined
   * First Published at: http://wiki.php.net/rfc/callable-types   * First Published at: http://wiki.php.net/rfc/callable-types
  
Line 9: Line 9:
  
 This RFC proposes an evolution of the **callable** type, scoped to argument lists. This should allow more detailed declarations This RFC proposes an evolution of the **callable** type, scoped to argument lists. This should allow more detailed declarations
-of **callable** "typehints" - including their **arity**, **argument types** and **return type**.+of **callable** type declarations - including their **arity**, **argument types** and **return type**.
  
 Here is one basic comparison between a simple **callable** and a detailed **callable prototype** declaration: Here is one basic comparison between a simple **callable** and a detailed **callable prototype** declaration:
Line 43: Line 43:
 }); });
 // >>> // >>>
-// TypeError: Argument 3 passed to reduce() must be callable(int, int):int, callable($a, $b, $c) given...+// TypeError: Argument 3 passed to reduce() must be compliant with callable(int, int):int, incompatible callable($a, $b, $c) given...
 </code> </code>
  
Line 59: Line 59:
  
 The very first example of this RFC already illustrates well why a detailed "Type Error" offers better debuggability when compared to The very first example of this RFC already illustrates well why a detailed "Type Error" offers better debuggability when compared to
-a trail of warnings and notices. In case warnings and notices get converted to exceptions, the scenario get'even less friendly+a trail of warnings and notices. In case warnings and notices get converted to exceptions, the scenario gets even less friendly
 as problems would be revealed one by one. as problems would be revealed one by one.
  
Line 73: Line 73:
 === Self Documented Code === === Self Documented Code ===
  
-It's common to see callback based libraries doing their best to declare the callable signatures on docblocks, hoping that+It's common to see callback based libraries doing their best to declare the callable signatures on doc comments, hoping that
 the consumers will be able to figure out what to pass around: the consumers will be able to figure out what to pass around:
  
Line 137: Line 137:
 }); });
 </code> </code>
 +
 +==== Nested callables ====
 +
 +Nested callables can be used with no imposed limit on the nesting level.
 +<code php>
 +function foo(callable(callable(int)) $cb) {
 +    $cb(function (int $i) {
 +        var_dump($i);
 +    });
 +}
 +
 +foo(function (callable(int) $intPrinter) {
 +    $intPrinter(123);
 +});
 +</code>
 +
 +There's currently no way to reference ''callable'' signature from within itself, meaning there's no way to make recursive signatures like below:
 +<code php>
 +function bar(callable(int $number): callable(int $number): callable(int $number): parent_callable $recursiveCb) { // this wouldn't work currently
 +}
 +</code>
 +
 +To add to that, nested callables can get pretty unreadable quickly, both of these problems would be best solved by a typedef feature of some kind, added to PHP later.
  
 ==== Variance and Signature Validation ==== ==== Variance and Signature Validation ====
  
-Variance is supported and adheres to LSP. This means that whenever function of type ''F'' is expected, any function that takes equal or more general input than ''F'' and gives equal or narrower output than ''F'', can be considered of type ''F''. Classes in argument/return type of a callable typehint are a subject to variance, primitives are not.+Variance is supported and adheres to LSP. This means that whenever function of type ''F'' is expected, any function that takes equal or more general input than ''F'' and gives equal or narrower output than ''F'', can be considered of type ''F''. Classes in argument/return type of a callable type declarations are a subject to variance, primitives are not.
  
 Examples: Examples:
Line 151: Line 174:
  
 foo(function (A $a) {}); // there's no variance in this case, A can be substituted by A foo(function (A $a) {}); // there's no variance in this case, A can be substituted by A
-foo(function (B $b) {}); // Uncaught TypeError: Argument 1 passed to foo() must be callable of compliant signature: callable(A), callable(B $b) given+foo(function (B $b) {}); // Uncaught TypeError: Argument 1 passed to foo() must be compliant with callable(A), incompatible callable(B $b) given
 bar(function (A $a) {}); // callable(A) > callable(B) - we can substitute callable(B) with callable(A) because the latter has a wider input than the latter bar(function (A $a) {}); // callable(A) > callable(B) - we can substitute callable(B) with callable(A) because the latter has a wider input than the latter
 </code> </code>
Line 157: Line 180:
 The same rules apply to return type of a callable: The same rules apply to return type of a callable:
 <code php> <code php>
-function foo(callable: A $cb) { }+function bar(callable() $cb) { } 
 +bar(function (): A { return new A; }); // it is valid to pass a function with declared return type as a parameter of `callable(..)` 
 + 
 +function foo(callable(): A $cb) { }
  
 foo(function (): A { return new A; }); // A == A foo(function (): A { return new A; }); // A == A
-foo(function (): B { return new B; }); // B < A this closure will return narrower type than what is expected by "foo", which means it can be a substitute for callable: A+foo(function (): B { return new B; }); // B < A this closure will return narrower type than what is expected by "foo", which means it can be a substitute for callable(): A
 </code> </code>
  
Line 169: Line 195:
 </code> </code>
  
-Optional arguments count just like any other arguments:+Optional parameters count just like any other parameters if they are typed:
 <code php> <code php>
 function foo(callable() $cb) { } function foo(callable() $cb) { }
Line 179: Line 205:
 // And callable(A $a) < callable(), so the call to foo() will fail here // And callable(A $a) < callable(), so the call to foo() will fail here
 </code> </code>
 +
 +Otherwise they can pass type check boundaries even if they are not defined in the callable type:
 +<code php>
 +function foo(callable() $cb) { }
 +foo(function ($a = 123) { }); // valid as it won't have a problem explained above
 +</code>
 +The same goes for variadics as they are a special kind of optional parameters.
  
 When callable type is nested (when you have ''callable(callable(A))'') variance has to be inversed with each nesting level. So if we have ''callable(A) > callable(B)'' then ''callable(callable(A)) < callable(callable(B))''. When callable type is nested (when you have ''callable(callable(A))'') variance has to be inversed with each nesting level. So if we have ''callable(A) > callable(B)'' then ''callable(callable(A)) < callable(callable(B))''.
 +
 +==== References in callables ====
 +
 +Reference parameters are supported: no variance is applied to the fact whether parameter is referential or not.
 +
 +Example:
 +<code php>
 +function foo(callable(&$byref) $cb) { }
 +
 +foo(function (&$bar) { }); // valid
 +foo(function ($bar) { }); // TypeError: Argument 1 passed to foo() must be compliant with callable(&$byref), incompatible callable($bar) given
 +
 +function bar(callable($byval) $cb) { }
 +
 +bar(function (&$bar) { }); // TypeError: Argument 1 passed to bar() must be compliant with callable($byval), incompatible callable(&$bar) given
 +</code>
 +
 +Functions returning a reference are compatible with functions returning a value for caller, hence both are interchangeable:
 +<code php>
 +function foo(callable(): A $cb) { }
 +
 +foo(function (): A { return new A; });
 +foo(function &(): A { static $a; $a = $a ?: new A; return $a; }); // both would pass the boundaries of a type check
 +</code>
 +
 +There's no way to declare that you expect a callable that returns reference due to syntax limitations.
 +
 +==== Parameters with default values ====
 +
 +It's not possible to declare default value of a parameter in a callable prototype. Because currently PHP doesn't consider parameter's default values in signature validation (their invariance is not enforced in overridden methods in classes).
  
 ==== Syntax Choices ==== ==== Syntax Choices ====
Line 198: Line 261:
 </code> </code>
  
-It's already common to see analogous syntax inside docblocks even though there are no regnant conventions:+It's already common to see analogous syntax inside doc comments even though there are no regnant conventions:
  
 <code php> <code php>
Line 266: Line 329:
 is to simply skip it. is to simply skip it.
  
-It's also notable that some types like 'void' should never be used as a callable return type. They simply impose unnecessary restrictions to callables +It's also notable that some types like 'void' should never be used as a callable return type. They simply impose unnecessary restrictions 
-in any imaginable use case. E.g:+to callables in any imaginable use case. E.g:
  
 <code php> <code php>
 function foo(callable($a, $b):void $callback) { function foo(callable($a, $b):void $callback) {
   //...   //...
 +}
 +</code>
 +
 +===== Reflection =====
 +
 +There are no BC-breaking changes in Reflection.
 +
 +Here are the changes needed for reflection:
 +
 +`ReflectionParameter::getType()` can now return instance of `ReflectionCallableType` which extends `ReflectionType`:
 +
 +<code php>
 +class ReflectionCallableType extends ReflectionType
 +{
 +    /**
 +     * Tells whether it's just a `callable` hint or if it has a prototype `callable(something): something`
 +     */
 +    public function hasPrototype(): bool;
 +
 +    /**
 +     * Returns a number of parameters required by a callable prototype
 +     */
 +    public function getArity(): bool;
 +
 +    /**
 +     * Returns an array of ReflectionCallableParameter instances
 +     */
 +    public function getParameters(): array;
 +
 +    /**
 +     * Tells whether the prototype has return type defined
 +     */
 +    public function hasReturnType(): bool;
 +
 +    /**
 +     * Returns return type of the callable prototype
 +     */
 +    public function getReturnType(): ReflectionType;
 +
 +    /**
 +     * Tells whether $value has compatible callable prototype. This is the easiest way
 +     * to implement runtime-error-free compatibility checks at this point...
 +     * Later we could implement it in the form of `instanceof`
 +     */
 +    public function isA($value);
 +}
 +
 +class ReflectionCallableParameter
 +{
 +    /**
 +     * Tells whether this callable parameter has a type
 +     */
 +    public function hasType(): bool;
 +
 +    /**
 +     * Returns a type of this callable parameter
 +     */
 +    public function getType(): ReflectionType;
 +
 +    /**
 +     * Tells whether callable parameter is named or not (e.g. callable(Foo $foo) vs callable(Foo))
 +     */
 +    public function hasName(): bool;
 +
 +    /**
 +     * Returns name of the callable parameter
 +     */
 +    public function getName(): string;
 +
 +    /**
 +     * Whether this is by-val or by-ref parameter
 +     */
 +    public function isPassedByReference(): bool;
 +    
 +    /**
 +     * Whether this is a variadic parameter
 +     */
 +    public function isVariadic(): bool;
 } }
 </code> </code>
Line 303: Line 444:
  
 Named callable types were deliberately left out the current proposal. The rationale behind this decision is that Named callable types were deliberately left out the current proposal. The rationale behind this decision is that
-most callable types are very transient, so the proposed inlined syntax will solve 90% of the use cases. This bit could be +most callable types are very transient, so the proposed inlined syntax will solve 90% of the use cases.
-added through another RFC and probably should, because if typedefs are to appear in PHP they should work not only for callable types but for all other types as well.+
  
-Another reason is that depending on how the [[https://wiki.php.net/rfc/union_types|Union Types]] RFC is designed it might include named types and, naturally, named callable types would be supported.+Another reason is that depending on how the [[https://wiki.php.net/rfc/union_types|Union Types]] RFC is designed it 
 +might include named types and, naturally, named callable types would be supported. This bit could be added through another RFC, 
 +and probably should, because if typedefs are to appear in PHP they should work not only for callable types but for all other types as well.
  
 The following could also be an option for a dedicated named callable type syntax if union types (or any other RFC introducing type aliasing) gets rejected: The following could also be an option for a dedicated named callable type syntax if union types (or any other RFC introducing type aliasing) gets rejected:
Line 322: Line 464:
 </code> </code>
  
-==== Reflection API ==== +Besides that, even with named callable types support, inlined callable types could be a way to keep the type unexposed from public API 
- +while PHP lacks first class packages.
-An extension to the reflection API will be proposed in case the RFC is approved.+
  
 ===== Votes ===== ===== Votes =====
  
-This RFC requires a 2/3 majority to pass.+This RFC requires a 2/3 majority to pass. Vote started on May 23, 2016, ends June 6, 2016. 
 + 
 +<doodle title="Accept callable prototypes?" auth="nikita2206" voteType="single" closed="true"> 
 +   * Yes 
 +   * No 
 +</doodle>
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
  
 The work in progress of the implementation can be found at https://github.com/php/php-src/pull/1633 The work in progress of the implementation can be found at https://github.com/php/php-src/pull/1633
 +
 +The patch can be tested through https://3v4l.org
  
 ===== Known Issues ===== ===== Known Issues =====
Line 338: Line 486:
 ==== Syntax ==== ==== Syntax ====
  
-There is a known syntax conflict with callable types that ommit argument names, as in:+There is a known syntax conflict with callable types that omit argument names, as in:
  
 <code php> <code php>
Line 347: Line 495:
 </code> </code>
  
-The following pull request would fix this arglist edge case https://github.com/php/php-src/pull/1667+The following pull request would fix these argument list edge cases https://github.com/php/php-src/pull/1667
  
 ===== References ===== ===== References =====
rfc/callable-types.1461280138.txt.gz · Last modified: 2017/09/22 13:28 (external edit)