rfc:protocol_type_hinting
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
rfc:protocol_type_hinting [2013/06/25 15:58] – [Syntax] ircmaxell | rfc:protocol_type_hinting [2013/06/26 15:16] – Update RFC, renaming to Structural Typing ircmaxell | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== PHP RFC: Protocol | + | ====== PHP RFC: Structural |
* Version: 0.1 | * Version: 0.1 | ||
* Date: 2013-06-25 | * Date: 2013-06-25 | ||
Line 10: | Line 10: | ||
Currently PHP uses an enforced interface style, where the argument object to a function or method must implement the respective interface. This RFC discusses inverting the dependency to allow for run-time resolving of interfaces. This is very similar to how GO implements interfaces. | Currently PHP uses an enforced interface style, where the argument object to a function or method must implement the respective interface. This RFC discusses inverting the dependency to allow for run-time resolving of interfaces. This is very similar to how GO implements interfaces. | ||
+ | In short, rather than checking the type-tree as traditional type-hinting works, this only checks to see that the **public** APIs match (that if you added " | ||
==== What Are Go-Style Interfaces? ==== | ==== What Are Go-Style Interfaces? ==== | ||
- | Go-Style interfaces (called //Protocols// by this RFC) are basically | + | Go-Style interfaces (called //Structural Typing// by this RFC) are basically interface |
==== Why Go-Style Interfaces? ==== | ==== Why Go-Style Interfaces? ==== | ||
Line 22: | Line 23: | ||
When you have cross-project dependencies. Currently, both packages must declare a dependency to a third package for the interface. A good example of this is the PSR-3 logger interface. Currently, the PSR-3 interface must be included by every project that declares a logger which you want to conform to the interface. This results in pulling in project-level dependencies even in cases where you don't need them. | When you have cross-project dependencies. Currently, both packages must declare a dependency to a third package for the interface. A good example of this is the PSR-3 logger interface. Currently, the PSR-3 interface must be included by every project that declares a logger which you want to conform to the interface. This results in pulling in project-level dependencies even in cases where you don't need them. | ||
- | Implementing | + | Implementing |
- | + | ||
- | === Duck Typing === | + | |
- | + | ||
- | Presently, to implement duck-typing, | + | |
- | + | ||
- | <file php duck_typing.php> | + | |
- | <?php | + | |
- | public function foo($logger) { | + | |
- | if (!method_exists($logger, | + | |
- | throw new Exception(' | + | |
- | } | + | |
- | $logger-> | + | |
- | } | + | |
- | ?> | + | |
- | </ | + | |
- | + | ||
- | However with protocols (go-style interfaces), | + | |
=== Decoupling Of Classes === | === Decoupling Of Classes === | ||
- | Right now, there' | + | Right now, there' |
<file php class_decoupling.php> | <file php class_decoupling.php> | ||
Line 54: | Line 38: | ||
This further strengthens the safety offered by type-hints, while decoupling the class further from the interface and classes of the hint. | This further strengthens the safety offered by type-hints, while decoupling the class further from the interface and classes of the hint. | ||
+ | |||
===== Proposal ===== | ===== Proposal ===== | ||
- | I propose adding a special syntax to the current type-hinting paradigm to allow for indicating a non-strict *instanceof* which would resolve the dependency as a protocol. | + | I propose adding a special syntax to the current type-hinting paradigm to allow for indicating a non-strict *instanceof* which would resolve the dependency as a Structural Type. |
==== Proposed Syntax ==== | ==== Proposed Syntax ==== | ||
- | The current proposed syntax is basically wrapping a traditional type hint (// | + | The current proposed syntax is basically wrapping a traditional type hint (// |
<file php duck_typing.php> | <file php duck_typing.php> | ||
Line 83: | Line 68: | ||
==== Proposed Behavior ==== | ==== Proposed Behavior ==== | ||
- | Any valid class-style identifier can be used as the "protocol | + | Any valid class-style identifier can be used as the "structure |
<file php duck_typing.php> | <file php duck_typing.php> | ||
Line 148: | Line 133: | ||
==== Raising Errors ==== | ==== Raising Errors ==== | ||
- | Currently, only a // | + | Currently, only a // |
==== Syntax ==== | ==== Syntax ==== | ||
- | I considered implementing a new " | + | I considered implementing a new " |
If the //< | If the //< | ||
Line 167: | Line 152: | ||
It's worth noting that since this is a separate branch, the current performance of normal type-hints remains uneffected. | It's worth noting that since this is a separate branch, the current performance of normal type-hints remains uneffected. | ||
- | The current Proof-Of-Concept will re-run the verification functions | + | Currently, // |
+ | |||
+ | While performance is an apparent concern, the benchmarks indicate performance at works on-par with existing type hints (and when called multiple times can be faster)... | ||
+ | |||
+ | Here are the results of the benchmarks: | ||
+ | |||
+ | <file php benchmark.php> | ||
+ | <?php | ||
+ | interface Foo { | ||
+ | public function foo(); | ||
+ | } | ||
+ | |||
+ | class Bar { | ||
+ | public function foo() {} | ||
+ | } | ||
+ | |||
+ | class Baz implements Foo { | ||
+ | public function foo() {} | ||
+ | } | ||
+ | |||
+ | function benchmark($func, | ||
+ | $s = microtime(true); | ||
+ | for ($i = 0; $i < $times; $i++) { | ||
+ | $func($arg); | ||
+ | } | ||
+ | $e = microtime(true); | ||
+ | return $e - $s; | ||
+ | } | ||
+ | $times = 1000000; | ||
+ | $interface = benchmark(function(Foo $foo) {}, $times, new Baz); | ||
+ | echo " | ||
+ | $structural = benchmark(function(< | ||
+ | echo " | ||
+ | $native = benchmark(function($foo) {}, $times, new Bar); | ||
+ | echo " | ||
+ | ?> | ||
+ | </ | ||
+ | |||
+ | === When Run Once === | ||
+ | |||
+ | When run once (with $times = 1): | ||
+ | |||
+ | '' | ||
+ | Structural in 1.4066696166992E-5 seconds, 1.4066696166992E-5 seconds per run | ||
+ | Native in 6.9141387939453E-6 seconds, 6.9141387939453E-6 seconds per run'' | ||
+ | |||
+ | The margin of error for the test is approximately the same difference as between Interface and Structural. This means that the performance for a single run is about constant. | ||
- | === Short-Circuit on Instanceof | + | === When Run Many Times === |
- | We can short-circuit if //$obj instanceof hint// returns true. This is likely going to be generally false, but checking is reasonably cheap, and will push the cost of the match to the compiler. | + | When run with $times = 1000000; |
- | === Cache Protocol Checks === | + | '' |
+ | Structural in 0.48089909553528 seconds, 4.8089909553528E-7 seconds per run | ||
+ | Native in 0.3850359916687 seconds, 3.850359916687E-7 seconds per run'' | ||
- | Once we get a valid result (either true or false), we can cache that. The outstanding question would be where to cache. On the protocol side? Or on the implementing side (if in the CE)? Or should there be an Executor Global setup for the cache (as a hash table). | + | In this case, the margin of error was less than the difference, meaning that the Structural approach is slightly more performant at runtime than the interface based approach. |
===== Unaffected PHP Functionality ===== | ===== Unaffected PHP Functionality ===== | ||
Line 194: | Line 227: | ||
* [[http:// | * [[http:// | ||
+ | * [[http:// | ||
+ | * [[http:// | ||
===== ChangeLog ===== | ===== ChangeLog ===== | ||
* 0.1 - Initial Draft | * 0.1 - Initial Draft |
rfc/protocol_type_hinting.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1