rfc:constants_in_traits

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:constants_in_traits [2022/06/29 15:26] – fix wording sjirfc:constants_in_traits [2022/08/04 19:17] (current) – Move the status to implemented sji
Line 1: Line 1:
 ====== PHP RFC: Constants in Traits ====== ====== PHP RFC: Constants in Traits ======
-  * Version: 0.1+  * Version: 0.2.1
   * Date: 2022-06-21   * Date: 2022-06-21
   * Author: Shinji Igarashi<sji@sj-i.dev>, Stephen Reay<stephen@koalephant.com>   * Author: Shinji Igarashi<sji@sj-i.dev>, Stephen Reay<stephen@koalephant.com>
-  * Status: Under Discussion+  * Status: Implemented
   * Implementation: https://github.com/php/php-src/pull/8888   * Implementation: https://github.com/php/php-src/pull/8888
   * First Published at: http://wiki.php.net/rfc/constants_in_traits   * First Published at: http://wiki.php.net/rfc/constants_in_traits
  
 ===== Introduction ===== ===== Introduction =====
-Traits are used for horizontal code reuse across classes, and currently allow the definition of methods and properties, but not constants. This means that it is not possible to define invariants expected by a trait in the trait itself. So currently, workarounds are required in some cases, such as defining constants in its composing class or an interface implemented by the composing class.+Traits [[https://www.php.net/manual/en/language.oop5.traits.php|[1]]] are used for horizontal code reuse across classes, and currently allow the definition of methods and properties, but not constants. This means that it is not possible to define invariants expected by a trait in the trait itself. So currently, workarounds are required in some cases, such as defining constants in its composing class or an interface implemented by the composing class.
  
 This RFC proposes to allow defining constants in traits in the same manner that is currently possible for properties, with the following use cases in mind. This RFC proposes to allow defining constants in traits in the same manner that is currently possible for properties, with the following use cases in mind.
Line 40: Line 40:
  
 <code php> <code php>
-intertface FooInterface {+interface FooInterface {
     public const FLAG_1 = 1;     public const FLAG_1 = 1;
     public function doFoo(int $flags): void;     public function doFoo(int $flags): void;
Line 66: Line 66:
 trait Foo { trait Foo {
     public const FLAG_1 = 1;     public const FLAG_1 = 1;
-    public const FLAG_2 = 2;+    protected const FLAG_2 = 2; 
 +    private const FLAG_3 = 2;
  
     public function doFoo(int $flags): void {     public function doFoo(int $flags): void {
Line 74: Line 75:
         if ($flags & self::FLAG_2) {         if ($flags & self::FLAG_2) {
             echo 'Got flag 2';             echo 'Got flag 2';
 +        }
 +        if ($flags & self::FLAG_3) {
 +            echo 'Got flag 3';
         }         }
     }     }
Line 80: Line 84:
  
 ==== Prohibit direct access through a trait name ==== ==== Prohibit direct access through a trait name ====
-Trait constants cannot be accessed through the name of the trait in a form like TraitName::CONSTANT. This is in line with the deprecation of accessing static members of traits directly [[https://wiki.php.net/rfc/deprecations_php_8_1#accessing_static_members_on_traits|]]. Trait constants must be accessed through the composing class. That is, they must be accessed through the composing class name, or its descendant class name, ''self'', ''static'', ''parent'', or its instance.+Trait constants cannot be accessed through the name of the trait in a form like TraitName::CONSTANT. This is in line with the deprecation of accessing static members of traits directly [[https://wiki.php.net/rfc/deprecations_php_8_1#accessing_static_members_on_traits|[2]]]. Trait constants must be accessed through the composing class. That is, they must be accessed through the composing class name, or its descendant class name, ''self'', ''static'', ''parent'', or its instance.
  
 <code php> <code php>
Line 218: Line 222:
         CONSTANT as private;         CONSTANT as private;
     }     }
 +}
 +</code>
 +
 +==== Can be used in Enum ====
 +Enumerations can use traits having constants, which behave as if the constants were defined within the enumeration.
 +
 +<code php>
 +trait T {
 +    private const CONSTANT = 42;
 +}
 +
 +// OK
 +enum E: int {
 +    use T;
 +
 +    case CaseA = self::CONSTANT;
 } }
 </code> </code>
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
-There are no backwards-incompatible changes in this RFC.+There are no backward-incompatible changes in this RFC.
  
 ===== Proposed PHP Version(s) ===== ===== Proposed PHP Version(s) =====
Line 230: Line 250:
  
 ==== Why were constants left out of traits initially ==== ==== Why were constants left out of traits initially ====
-It's an old story and no one remembers why, but it was probably simply overlooked as there was barely any mention of constants in traits in the old ML discussions.+It's an old story and no one remembers why, but it was probably simply overlooked as there was barely any mention of constants in traits in the old ML discussions [[https://marc.info/?l=php-internals&w=4&r=1&s=trait+const&q=b|[3]]][[https://marc.info/?l=php-internals&w=4&r=1&s=trait+constant&q=b|[4]]][[https://marc.info/?l=php-internals&w=4&r=1&s=trait+constants&q=b|[5]]][[https://marc.info/?l=php-internals&w=4&r=2&s=traits+const&q=b|[6]]][[https://marc.info/?l=php-internals&w=4&r=2&s=traits+constant&q=b|[7]]][[https://marc.info/?l=php-internals&w=4&r=3&s=traits+constants&q=b|[8]]].
  
-[[https://externals.io/message/118039#118059|The response from the original author of the trait RFC]] is quoted below.+The response from the original author of the trait RFC[[https://wiki.php.net/rfc/horizontalreuse|[9]]] is quoted below [[https://externals.io/message/118039#118059|[10]]].
  
 <blockquote> <blockquote>
Line 272: Line 292:
 While the main use case of trait methods is to be invoked from outside of the trait, the main use case of a trait constant is to serve as a member that is referenced by methods in the trait. Therefore, the same way of conflict resolution as for methods is not very useful. While the main use case of trait methods is to be invoked from outside of the trait, the main use case of a trait constant is to serve as a member that is referenced by methods in the trait. Therefore, the same way of conflict resolution as for methods is not very useful.
  
-Currently, PHP has held off on better conflict resolution for trait properties for the last decade, and simply marks multiple incompatible definitions as errors, as an obvious sign of a mistake. One idea to address this issue is to introduce new visibility "trait local" that are only accessible within a given trait ([[https://externals.io/message/35800]]). This is beyond the scope of this proposal and would require a separate RFC.+Currently, PHP has held off on better conflict resolution for trait properties for the last decade, and simply marks multiple incompatible definitions as errors, as an obvious sign of a mistake. One idea to address this limitation is to introduce new visibility "trait local" that are only accessible within a given trait [[https://externals.io/message/35800|[11]]]. This is beyond the scope of this proposal and would require a separate RFC.
  
 Constants in PHP can hold state through object constants and are more similar to properties than methods. Both constants and properties should have the same style of conflict resolution. Therefore, for now, this RFC only proposes that trait constants have the same restrictions as properties. Constants in PHP can hold state through object constants and are more similar to properties than methods. Both constants and properties should have the same style of conflict resolution. Therefore, for now, this RFC only proposes that trait constants have the same restrictions as properties.
  
 ==== Why are those compatibility checks performed on properties in the first place ==== ==== Why are those compatibility checks performed on properties in the first place ====
-It's basically a way to deal with state conflicts in the diamond problem.+It's basically a way to deal with state conflicts in multiple inheritance.
  
 If we were to allow some "overrides" for trait properties, for example, we might have to decide which definitions would "win out" at each location. If we were to allow some "overrides" for trait properties, for example, we might have to decide which definitions would "win out" at each location.
Line 283: Line 303:
 There can be more than one policy for handling state conflicts, and PHP has implemented one restricted approach for now and has not yet addressed another policy after that. There can be more than one policy for handling state conflicts, and PHP has implemented one restricted approach for now and has not yet addressed another policy after that.
  
-It should be noted that in the original trait paper, traits have only behavior and no state, thereby avoiding the state conflict problem in diamond inheritance. In the original trait paper, it is assumed that the state is provided on the composing class side and accessed from traits through accessors [[https://dl.acm.org/doi/10.1145/1119479.1119483]]. This pure approach guides too much boilerplate in creating and using traits. +It should be noted that in the original trait paper, traits have only behavior and no state, thereby avoiding the state conflict problem in multiple inheritance. In the original trait paper, it is assumed that the state is provided on the composing class side and accessed from traits through accessors [[https://www.cs.cmu.edu/~aldrich/courses/819/Scha03aTraits.pdf|[12]]]. This pure approach guides too much boilerplate in creating and using traits. 
  
-Historically, there have been two typical approaches to state conflicts in the diamond problem: one is to merge the state of the common ancestor, and the other is to have an independent state for each common ancestor in separate "paths" and provide a way to select one. Since different use cases require one or the other, programming languages sometimes have features that allow programmers to use these two methods selectively, such as virtual inheritance in C++.+Historically, there have been two typical approaches to state conflicts in multiple inheritance like the diamond problem: one is to merge states having the same name, and the other is to have an independent state for each ancestor in separate "paths" and provide a way to select one. Since different use cases require one or the other, programming languages sometimes have features that allow programmers to use these two methods selectively, such as virtual inheritance in C++.
  
-Where having a state becomes tricky is when the diamond problem occurs. If there are no conflicts, it does not matter if a trait has state. And even if there is a conflict, if the programming language defaults to either merge or having an independent state, that default will work fine for half of the use cases.+Where having a state becomes tricky is when conflicts occur. If there are no conflicts, it does not matter if a trait has state. And even if there is a conflict, if the programming language defaults to either merge or having an independent state, that default will work fine for half of the use cases.
  
-PHP strikes a balance in this problem, allowing traits to define properties, and choosing to deal with conflicts by merging states, and also marking any conflicting definitions with different visibility or default values as an error, as a sign of an unintended name conflict [[https://externals.io/message/51007#51072]].+PHP strikes a balance in this problem, allowing traits to define properties, choosing to deal with conflicts by merging states, and also marking any conflicting definitions with different visibility or default values as an error, as a sign of an unintended name conflict [[https://externals.io/message/51007#51072|[13]]].
  
 ==== Why not introduce visibility changes like methods ==== ==== Why not introduce visibility changes like methods ====
Line 310: Line 330:
 A survey of the 1149 packages on packagist shows that at least 222 locations in 26 packages use this feature for trait methods. A survey of the 1149 packages on packagist shows that at least 222 locations in 26 packages use this feature for trait methods.
  
-We refrain from judging whether this is large or small, but there are probably cases where it is more convenient for classes to be able to decide which members to expose.+We refrain from judging whether this is large or small, but there are probably cases where it is more convenient for composing classes to be able to decide which members to expose.
  
 We do not preclude the future introduction of this feature, but for the sake of simplicity, we do not include it in this RFC. We do not preclude the future introduction of this feature, but for the sake of simplicity, we do not include it in this RFC.
Line 318: Line 338:
 ===== Comparison to other languages ===== ===== Comparison to other languages =====
 ==== Hack ==== ==== Hack ====
-Hack gives priority to the definition of the trait used first when multiple trait constants conflict. If there is a conflict with a parent class, the parent class definition takes precedence, and if there is a conflict with an interface definition, it results in a type error. [[https://docs.hhvm.com/hack/traits-and-interfaces/using-a-trait#resolution-of-naming-conflicts]]+Hack gives priority to the definition of the trait used first when multiple trait constants conflict. If there is a conflict with a parent class, the parent class definition takes precedence, and if there is a conflict with an interface definition, it results in a type error. [[https://docs.hhvm.com/hack/traits-and-interfaces/using-a-trait#resolution-of-naming-conflicts|[14]]]
  
-===== Proposed Voting Choices ===== +==== Java ==== 
-A 2/3 majority is needed for this RFC to pass. Voting will start on 5. July 2022 and end on 19. July 2022+In Java, variables can be defined in interfaces, which are public, static, and final by default, i.e., equivalent to constants in PHP. If a Java class implements multiple interfaces at the same time and their variables conflict, this is not an error in itself, but the programmer must explicitly specify which definition to choose when referencing those variables. If the same interface definition appears more than once via the diamond problem, the member is inherited only once [[https://docs.oracle.com/javase/specs/jls/se18/html/jls-9.html#jls-9.3|[15]]]. 
 + 
 +==== Kotlin ==== 
 +In Kotlin, a class can implement multiple interfaces at the same time, and properties can be defined in interfaces. However, interface properties do not have actual backing fields. That is, they are abstract by default and must have their actual state in the implementing class, or provide accessors, with collision resolution if necessary [[https://kotlinlang.org/docs/interfaces.html#interfaces-inheritance|[16]]]. 
 + 
 +==== Rust ==== 
 +Rust has associated constants. Associated constants are constants associated with a type. If multiple definitions of the same name conflict, they must be disambiguated on reference using qualified paths [[https://doc.rust-lang.org/reference/items/associated-items.html#associated-constants|[17]]][[https://doc.rust-lang.org/reference/paths.html#qualified-paths|[18]]]. 
 + 
 +==== C++ ==== 
 +In C++, multiple inheritance is possible, and equivalents of constants in PHP can be defined as static consts within classes. Members of the same name usually have different entities for each class, but by specifying virtual at the time of inheritance, the entities of common classes in diamond inheritance can be merged into one. If multiple member definitions of the same name conflict, they must be disambiguated on reference [[https://timsong-cpp.github.io/cppwp/n4861/class.mi|[19]]][[https://timsong-cpp.github.io/cppwp/n4861/class.member.lookup|[20]]]. 
 + 
 +==== Scala ==== 
 +Scala allows multiple traits to be used simultaneously on an object, linearized into a single inheritance hierarchy based on the order in which they are specified. Constants can be defined with the keyword ''val''. Attempting to mix multiple traits with a constant of the same name will result in a compile error. This can be solved by overriding on the mixing class. Also, if the val of each trait is declared as override beforehand, it can be overridden automatically according to the order of linearization [[https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html|[21]]]. 
 + 
 + 
 +===== Vote ===== 
 +A 2/3 majority is needed for this RFC to pass. Voting started on 5. July 2022 and end on 19. July 2022 
 + 
 +<doodle title="Allow constants in traits as proposed?" auth="sji" voteType="single" closed="true"> 
 +   * Yes 
 +   * No 
 +</doodle> 
 + 
 + 
 +===== Future Scope ===== 
 + 
 +==== Trait Local ==== 
 +Since constants and properties of traits are primarily intended to be referenced from within traits, it is more useful to be able to restrict the scope to trait local and prevent conflicts in the first place. This is also true for methods that presume references from inside the trait itself, such as recursions. 
 + 
 +Stateful Traits [[https://link.springer.com/chapter/10.1007/978-3-540-71836-9_4|[22]]] default to trait state as trait local, but allow the programmer to selectively use merge behavior as needed. On the contrary, since PHP defaults to merge behavior, there may be a future extension that allows trait local to be explicitly declared. This option has even been mentioned in the old discussions, but it has not caught the attention of many people and has been on hold for more than a decade [[https://externals.io/message/35800|[11]]]. Perhaps it is time to reconsider this also. 
 + 
 +==== Requiring specific interfaces or classes on traits ==== 
 +Hack can require that the composing class of a trait be a derived class of a specific class or an implementation of a specific interface [[https://docs.hhvm.com/hack/traits-and-interfaces/trait-and-interface-requirements|[23]]]. 
 + 
 +At the same time, Hack also allows to implement interfaces from traits. That is, if a class C uses a trait T that implements an interface I, then C is a subtype of I [[https://github.com/facebook/hhvm/commit/6a2c5150edf4c6d3d1e015d665d85221b0975f45|[24]]][[https://github.com/facebook/hhvm/commit/6dac173dd5cc56c67303f7e6a46917f0aebad773|[25]]]. Actually, trait constants in Hack were introduced as a syntactic sugar for traits that implement interfaces with constants [[https://github.com/facebook/hhvm/commit/9dcc664cd19d71c849be8ce052b11175d275c331|[26]]].  
 + 
 +These can be other ways to tie properties or constants to traits, and this RFC does not preclude future implementation of any of them.
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
Line 327: Line 383:
  
 ===== References ===== ===== References =====
 +  * [[https://www.php.net/manual/en/language.oop5.traits.php|[1]]] The explanation of traits in the PHP manual
 +  * [[https://wiki.php.net/rfc/deprecations_php_8_1#accessing_static_members_on_traits|[2]]] PHP RFC: Deprecations for PHP 8.1 | Accessing static members on traits
 +  * [[https://marc.info/?l=php-internals&w=4&r=1&s=trait+const&q=b|[3]]] marc.info | search results of "trait+const" in the archive of php-internals
 +  * [[https://marc.info/?l=php-internals&w=4&r=1&s=trait+constant&q=b|[4]]] marc.info | search results of "trait+constant" in the archive of php-internals 
 +  * [[https://marc.info/?l=php-internals&w=4&r=1&s=trait+constants&q=b|[5]]] marc.info | search results of "trait+constants" in the archive of php-internals
 +  * [[https://marc.info/?l=php-internals&w=4&r=2&s=traits+const&q=b|[6]]] marc.info | search results of "traits+const" in the archive of php-internals
 +  * [[https://marc.info/?l=php-internals&w=4&r=2&s=traits+constant&q=b|[7]]] marc.info | search results of "traits+constant" in the archive of php-internals
 +  * [[https://marc.info/?l=php-internals&w=4&r=3&s=traits+constants&q=b|[8]]] marc.info | search results of "traits+constants" in the archive of php-internals
 +  * [[https://wiki.php.net/rfc/horizontalreuse|[9]]] Request for Comments: Horizontal Reuse for PHP
 +  * [[https://externals.io/message/118039#118059|[10]]] externals.io | [RFC] [Under Discussion] Constants in traits | The current discussion about constants in traits
 +  * [[https://externals.io/message/35800|[11]]] externals.io | How to build a real Trait thing without exclusion and renaming | The future introduction of trait-local was considered and put on hold in the initial discussion
 +  * [[https://www.cs.cmu.edu/~aldrich/courses/819/Scha03aTraits.pdf|[12]]] Traits: Composable Units of Behaviour* | The original trait paper
 +  * [[https://externals.io/message/51007#51072|[13]]] externals.io | Traits and Properties | Discussion of trait properties leading to the current form
 +  * [[https://docs.hhvm.com/hack/traits-and-interfaces/using-a-trait#resolution-of-naming-conflicts|[14]]] HHVM and Hack Documentation | Traits And Interfaces: Using A Trait | Resolution of naming conflicts
 +  * [[https://docs.oracle.com/javase/specs/jls/se18/html/jls-9.html#jls-9.3|[15]]] Java Language Specification | 9.3. Field (Constant) Declarations
 +  * [[https://kotlinlang.org/docs/interfaces.html#interfaces-inheritance|[16]]] Kotlin docs | Interfaces Inheritance
 +  * [[https://doc.rust-lang.org/reference/items/associated-items.html#associated-constants|[17]]] The Rust Reference | Associated Constants
 +  * [[https://doc.rust-lang.org/reference/paths.html#qualified-paths|[18]]] The Rust Reference | Qualified paths
 +  * [[https://timsong-cpp.github.io/cppwp/n4861/class.mi|[19]]] Working Draft, Standard for Programming Language C++ n4861 | Multiple base classes
 +  * [[https://timsong-cpp.github.io/cppwp/n4861/class.member.lookup|[20]]] Working Draft, Standard for Programming Language C++ n4861 | Member name lookup
 +  * [[https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html|[21]]] Scala Language Specification Version 2.13 | Classes and Objects
 +  * [[https://link.springer.com/chapter/10.1007/978-3-540-71836-9_4|[22]]] Stateful Traits
 +  * [[https://docs.hhvm.com/hack/traits-and-interfaces/trait-and-interface-requirements|[23]]] HHVM and Hack Documentation | Traits And Interfaces: Trait And Interface Requirements
 +  * [[https://github.com/facebook/hhvm/commit/6a2c5150edf4c6d3d1e015d665d85221b0975f45|[24]]] facebook/hhvm | Allow traits to implement interfaces
 +  * [[https://github.com/facebook/hhvm/commit/6dac173dd5cc56c67303f7e6a46917f0aebad773|[25]]] facebook/hhvm | Allow traits to implement interfaces Add runtime support for implementing interfaces from traits
 +  * [[https://github.com/facebook/hhvm/commit/9dcc664cd19d71c849be8ce052b11175d275c331|[26]]] facebook/hhvm | Allow constants in traits
   * https://externals.io/message/110741 The initial discussion about constants in traits   * https://externals.io/message/110741 The initial discussion about constants in traits
-  * https://externals.io/message/51007 The original discussion about properties in traits +
-  * https://www.php.net/manual/en/language.oop5.traits.php The explanation of traits in the PHP manual +
-  * https://externals.io/message/35800 The discussion on "trait local" 14 years ago+
rfc/constants_in_traits.1656516411.txt.gz · Last modified: 2022/06/29 15:26 by sji