rfc:callable-types
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
rfc:callable-types [2016/04/21 23:08] – fix formatting error marcio | rfc:callable-types [2017/09/22 13:28] (current) – external edit 127.0.0.1 | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== PHP RFC: Callable | + | ====== PHP RFC: Callable |
- | * Version: | + | * Version: |
* Date: 2015-08-27 | * Date: 2015-08-27 | ||
* Authors: Nikita Nefedov < | * Authors: Nikita Nefedov < | ||
- | * Status: | + | * Status: |
* First Published at: http:// | * First Published at: http:// | ||
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** | + | of **callable** |
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, | + | // TypeError: Argument 3 passed to reduce() must be compliant with callable(int, |
</ | </ | ||
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 | + | a trail of warnings and notices. In case warnings and notices get converted to exceptions, the scenario |
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: | ||
}); | }); | ||
</ | </ | ||
+ | |||
+ | ==== 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); | ||
+ | }); | ||
+ | </ | ||
+ | |||
+ | There' | ||
+ | <code php> | ||
+ | function bar(callable(int $number): callable(int $number): callable(int $number): parent_callable $recursiveCb) { // this wouldn' | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 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 '' | + | Variance is supported and adheres to LSP. This means that whenever function of type '' |
Examples: | Examples: | ||
Line 151: | Line 174: | ||
foo(function (A $a) {}); // there' | foo(function (A $a) {}); // there' | ||
- | foo(function (B $b) {}); // Uncaught TypeError: Argument 1 passed to foo() must be callable of compliant | + | foo(function (B $b) {}); // Uncaught TypeError: Argument 1 passed to foo() must be compliant |
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 | ||
</ | </ | ||
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: | + | 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(function (): B { return new B; }); // B < A this closure will return narrower type than what is expected by " |
</ | </ | ||
Line 169: | Line 195: | ||
</ | </ | ||
- | Optional | + | Optional |
<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 | ||
</ | </ | ||
+ | |||
+ | 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 | ||
+ | </ | ||
+ | The same goes for variadics as they are a special kind of optional parameters. | ||
When callable type is nested (when you have '' | When callable type is nested (when you have '' | ||
+ | |||
+ | ==== 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(& | ||
+ | |||
+ | foo(function (&$bar) { }); // valid | ||
+ | foo(function ($bar) { }); // TypeError: Argument 1 passed to foo() must be compliant with callable(& | ||
+ | |||
+ | function bar(callable($byval) $cb) { } | ||
+ | |||
+ | bar(function (&$bar) { }); // TypeError: Argument 1 passed to bar() must be compliant with callable($byval), | ||
+ | </ | ||
+ | |||
+ | Functions returning a reference are compatible with functions returning a value for caller, hence both are interchangeable: | ||
+ | <code php> | ||
+ | function foo(callable(): | ||
+ | |||
+ | 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 | ||
+ | </ | ||
+ | |||
+ | There' | ||
+ | |||
+ | ==== Parameters with default values ==== | ||
+ | |||
+ | It's not possible to declare default value of a parameter in a callable prototype. Because currently PHP doesn' | ||
==== Syntax Choices ==== | ==== Syntax Choices ==== | ||
Line 198: | Line 261: | ||
</ | </ | ||
- | It's already common to see analogous syntax inside | + | It's already common to see analogous syntax inside |
<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 ' | + | It's also notable that some types like ' |
- | in any imaginable use case. E.g: | + | to callables in any imaginable use case. E.g: |
<code php> | <code php> | ||
function foo(callable($a, | function foo(callable($a, | ||
//... | //... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== Reflection ===== | ||
+ | |||
+ | There are no BC-breaking changes in Reflection. | ||
+ | |||
+ | Here are the changes needed for reflection: | ||
+ | |||
+ | `ReflectionParameter:: | ||
+ | |||
+ | <code php> | ||
+ | class ReflectionCallableType extends ReflectionType | ||
+ | { | ||
+ | /** | ||
+ | * Tells whether it's just a `callable` hint or if it has a prototype `callable(something): | ||
+ | */ | ||
+ | public function hasPrototype(): | ||
+ | |||
+ | /** | ||
+ | * Returns a number of parameters required by a callable prototype | ||
+ | */ | ||
+ | public function getArity(): bool; | ||
+ | |||
+ | /** | ||
+ | * Returns an array of ReflectionCallableParameter instances | ||
+ | */ | ||
+ | public function getParameters(): | ||
+ | |||
+ | /** | ||
+ | * Tells whether the prototype has return type defined | ||
+ | */ | ||
+ | public function hasReturnType(): | ||
+ | |||
+ | /** | ||
+ | * Returns return type of the callable prototype | ||
+ | */ | ||
+ | public function getReturnType(): | ||
+ | |||
+ | /** | ||
+ | * 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(): | ||
+ | | ||
+ | /** | ||
+ | * Whether this is a variadic parameter | ||
+ | */ | ||
+ | public function isVariadic(): | ||
} | } | ||
</ | </ | ||
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:// | + | Another reason is that depending on how the [[https:// |
+ | 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: | ||
</ | </ | ||
- | ==== Reflection API ==== | + | Besides that, even with named callable types support, inlined callable types could be a way to keep the type unexposed from public |
- | + | while PHP lacks first class packages. | |
- | An extension | + | |
===== 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=" | ||
+ | * Yes | ||
+ | * No | ||
+ | </ | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
The work in progress of the implementation can be found at https:// | The work in progress of the implementation can be found at https:// | ||
+ | |||
+ | The patch can be tested through https:// | ||
===== 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: | ||
</ | </ | ||
- | The following pull request would fix this arglist | + | The following pull request would fix these argument list edge cases https:// |
===== References ===== | ===== References ===== |
rfc/callable-types.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1