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 [2022/09/28 23:18] – Fix isEmpty impl 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: Draft+  * 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 9: Line 9:
 ===== Introduction ===== ===== Introduction =====
 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, 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 a 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 26: 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 36: 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 57: 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 63: Line 142:
 If you do use the feature: If you do use the feature:
   * Adding a default implementation to an existing interface method will not break existing code, because every existing usage has a higher priority than the default.   * Adding a default implementation to an existing interface method will not break existing code, because every existing usage has a higher priority than the default.
-  * If you add a new method to an interface, there is a compatibility break. The impact of the break is limited to places where the usage of the interface has a method of the same name.+  * If you add a new method to an interface, there is a compatibility break. The impact of the break is limited to places where the implementor/inheritor of the interface has a method of the same name.
  
 ===== 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 87: 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.1664407105.txt.gz · Last modified: 2022/09/28 23:18 by levim