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:48] – more minor fixes 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: 1.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 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 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 ====
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 272: Line 335:
 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 326: Line 467:
 while PHP lacks first class packages. while PHP lacks first class packages.
  
-==== Reflection API ====+===== Votes =====
  
-An extension to the reflection API will be proposed in case the RFC is approved. +This RFC requires a 2/3 majority to pass. Vote started on May 23, 2016, ends June 6, 2016.
- +
-===== Votes =====+
  
-This RFC requires a 2/3 majority to pass.+<doodle title="Accept callable prototypes?" auth="nikita2206" voteType="single" closed="true"> 
 +   * Yes 
 +   * No 
 +</doodle>
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
Line 364: Line 506:
     - Scala     - Scala
     - Swift https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Types.html     - Swift https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Types.html
- 
rfc/callable-types.1461282493.txt.gz · Last modified: 2017/09/22 13:28 (external edit)