rfc:interface-default-methods

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:interface-default-methods [2023/06/15 03:44] – Moving from draft to under discussion levimrfc:interface-default-methods [2023/07/17 15:06] (current) – Status: declined levim
Line 1: Line 1:
 ====== PHP RFC: Interface Default Methods ====== ====== PHP RFC: Interface Default Methods ======
-  * Version: 0.9+  * Version: 2.0
   * Date: 2022-06-27   * Date: 2022-06-27
   * Author: Levi Morrison, levim@php.net   * Author: Levi Morrison, levim@php.net
-  * Status: Under Discussion+  * Status: Declined
   * First Published at: http://wiki.php.net/rfc/interface-default-methods   * First Published at: http://wiki.php.net/rfc/interface-default-methods
  
Line 10: Line 10:
 Over time, authors sometimes want to add methods to an interface. Today, this causes large breakages to every implementor. This RFC proposes a way to reduce the scale of breakage. Over time, authors sometimes want to add methods to an interface. Today, this causes large breakages to every implementor. This RFC proposes a way to reduce the scale of breakage.
  
-Additionally, some times interfaces can implement some functionality of their interface in terms of other parts. For example, the interface ''Countable'' could implement <php>function isEmpty(): bool</php> by using <php>$this->count() == 0</php>. Today, traits are often used to accomplish this, but default interface implementation could be used instead.+Additionally, sometimes interfaces can implement some functionality of their interface in terms of other parts of their interface. For example, the interface ''Countable'' could implement <php>function isEmpty(): bool</php> by using <php>$this->count() == 0</php>. Today, traits are often used to accomplish this, but default interface implementation could be used instead, and would be simpler. When interfaces are designed with this technique in mind, it can enable productivity of the interface over time. 
 + 
 +For example, see [[https://doc.rust-lang.org/std/iter/trait.Iterator.html|Rust's Iterator trait]] which has more than 40 functions with default implementations and the implementor mostly needs to implement a single function, ''next(): Option<T>''. These functions with default behaviors have been stabilized over time with little to no breakage in practice. For example, ''for_each'' was added in v1.21 and ''map_while'' was added in v1.57.
  
 ===== Proposal ===== ===== Proposal =====
  
-Interface methods can now provide a default implementation.+Interface methods can now provide a method body. This implementation will be used if the implementer does not otherwise provide an implementation. Default implementations are available for both instance and static methods.
  
 <PHP> <PHP>
Line 28: Line 30:
 } }
 </PHP> </PHP>
 +
 +Interface methods can also now be <php>private</php> if they a have method body. The purpose here is to allow helper routines if the default implementations of the <php>public</php> methods need them.
 +
 +The methods with default implementations are inherited similarly to abstract base classes, as opposed to how traits behave. For instance, a private method on the interface is not accessible to the class which implements the interface; with a trait it would be.
  
 ==== Default Method Resolution ==== ==== Default Method Resolution ====
Line 38: Line 44:
   - If the above rules fail, then the class needs to implement the method itself, possibly delegating to one of the interfaces.   - If the above rules fail, then the class needs to implement the method itself, possibly delegating to one of the interfaces.
  
-This RFC proposes the same. However, rule 2 has not yet been implemented so I am unsure how feasible it is.+This RFC proposes the same. However, rule 2 is only partially implemented at this time.
  
 Here's an example of a class delegating to another method: Here's an example of a class delegating to another method:
Line 59: Line 65:
 (new Class1())->method1(); // Interface1::method1 (new Class1())->method1(); // Interface1::method1
 </PHP> </PHP>
 +
 +==== Parent Scoping ====
 +In interface default methods, you cannot use the <php>parent::</php> scoping mechanism. The following does not work:
 +
 +<PHP>
 +interface Interface1 {
 +    function method1() { echo __METHOD__, PHP_EOL; }
 +}
 +
 +interface Interface2 extends Interface1 {
 +    function method1() { parent::method1(); }
 +}
 +</PHP>
 +
 +The reason is that interfaces can extend multiple other interfaces e.g.
 +
 +<PHP>
 +interface Interface3 extends Interface1, Interface2 {}
 +</PHP>
 +
 +The name of the interface should be used instead:
 +
 +<PHP>
 +interface Interface2 extends Interface1 {
 +    function method1() { Interface1::method1(); }
 +}
 +</PHP>
 +
 +However, if a sub-class of the inheriting class calls ''parent::method1()'', it will work:
 +
 +<PHP>
 +interface Interface1 {
 +    function method1() { echo __METHOD__, PHP_EOL; }
 +}
 +
 +class Class1 implements Interface1 {
 +    // Inherits Interface1::method1() here.
 +}
 +
 +class Class2 extends Class1 {
 +    function method1() { parent::method1(); }
 +}
 +
 +(new Class2())->method1);
 +// output:
 +// Interface1::method1
 +</PHP>
 +
 +==== Cancelling Default Methods ====
 +If an interface method extends a parent interface method which has a default, this prevents using the default method for classes which implement the child interface but do not directly implement the parent one:
 +
 +<PHP>
 +interface Interface1 {
 +    function method1() { echo __METHOD__, "\n"; }
 +}
 +
 +interface Interface2 extends Interface1 {
 +    function method1();
 +}
 +
 +/* Would be an error because method1 has not been implemented.
 +class Class1 implements Interface2 {
 +    // error: method1 has not been implemented.
 +}
 + */
 +
 +// This is subtly different, but valid:
 +class Class1 implements Interface1, Interface2 {}
 +</PHP>
 +
 +The behavior could go either way. I picked this behavior because if it's wrong, it's easier to correct than the other way around.
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
Line 68: Line 145:
  
 ===== Proposed PHP Version(s) ===== ===== Proposed PHP Version(s) =====
-PHP 8.NEXT.+PHP 8.3.
  
 ===== RFC Impact ===== ===== RFC Impact =====
  
-==== To Existing Extensions ==== +==== To Extensions ==== 
-Modules can specify an interface implementation as well.+Modules can specify an interface implementation as well. These internal default methods should not be marked with ''ZEND_ACC_ABSTRACT'' and should be instead marked with ''ZEND_ACC_DEFAULT_METHOD''.
  
 ==== To Opcache ==== ==== To Opcache ====
-Opcache should also work with this feature.+Opcache should also work with this feature. The proof of concept implementation has not triggered any issues so far in CI. 
 + 
 +==== To the Ecosystem ==== 
 +Previously, interface methods were not allowed to have method bodies. These tools such as parsers, code analyzers, etc will need to be updated.
  
 ===== Open Issues ===== ===== Open Issues =====
Line 89: Line 169:
 ===== Voting ===== ===== Voting =====
 The vote will be a simple yes/no vote on whether to include the feature. The vote will be a simple yes/no vote on whether to include the feature.
 +<doodle title="Interface Default Methods" auth="levim" voteType="single" closed="false" closeon="2023-07-17T00:00:00Z">
 +   * Yes
 +   * No
 +</doodle>
 +
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
  
-WIP: https://github.com/php/php-src/compare/master...morrisonlevi:php-src:interface-default-methods+Here is a work-in-progress pull request: https://github.com/php/php-src/pull/11467.
  
 ===== Implementation ===== ===== Implementation =====
rfc/interface-default-methods.1686800640.txt.gz · Last modified: 2023/06/15 03:44 by levim