rfc:enumerations_and_adts
Differences
This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
rfc:enumerations_and_adts [2020/09/19 16:14] – created crell | rfc:enumerations_and_adts [2020/12/04 23:26] (current) – crell | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== PHP RFC: Enumerations and Algebraic Data Types ====== | ====== PHP RFC: Enumerations and Algebraic Data Types ====== | ||
- | * Version: 0.9 | + | |
* Date: 2020-09-19 | * Date: 2020-09-19 | ||
* Author: Larry Garfield (larry@garfieldtech.com), | * Author: Larry Garfield (larry@garfieldtech.com), | ||
* Status: Draft | * Status: Draft | ||
- | * First Published at: http://wiki.php.net/ | + | * Target Version: PHP 8.1 |
+ | * Implementation: TBD | ||
+ | |||
+ | This RFC has been supplanted by [[rfc: | ||
+ | |||
+ | Please ignore this page. | ||
===== Introduction ===== | ===== Introduction ===== | ||
- | This RFC introduces Enumerations to PHP. Specifically, | + | This RFC introduces Enumerations to PHP. Specifically, |
- | Many languages have support for enumerations of some variety. | + | Many languages have support for enumerations of some variety. A [[https:// |
- | The specific implementation here draws inspiration primarily from Swift, Rust, and Kotlin, but is not (nor is it intended as) a perfect 1:1 port of any of them. | + | The specific implementation here draws inspiration primarily from Swift, Rust, and Kotlin, but is not (nor is it intended as) a perfect 1:1 port of any of them. Enumerations take many forms depending on the language, and we opted to implement the most robust combination of functionality feasible. Every piece of functionality described here exists in a similar form in at least one, usually several, other enumeration-supporting languages. It is implemented as a single RFC rather than a series of RFCs as the functionality all inter-relates, |
+ | The most popular case of enumerations is '' | ||
===== Proposal ===== | ===== Proposal ===== | ||
+ | ==== Basic enumerations ==== | ||
+ | This RFC introduces a new language construct, '' | ||
- | All the features | + | <code php> |
+ | enum Suit { | ||
+ | case Hearts; | ||
+ | case Diamonds; | ||
+ | case Clubs; | ||
+ | case Spades; | ||
+ | } | ||
+ | </ | ||
+ | This declaration creates a new enumerated type named '' | ||
- | To [[http:// | + | < |
- | for inclusion in one of the world' | + | $val = Suit:: |
- | Remember that the RFC contents should be easily reusable in the PHP Documentation. | + | function pick_a_card(Suit $suit) { ... } |
- | If applicable, you may wish to use the language specification as a reference. | + | pick_a_card($val); |
+ | pick_a_card(Suit:: | ||
+ | pick_a_card(' | ||
+ | </ | ||
+ | In the simple case, multiple cases may be defined on a single line. The following is semantically equivalent | ||
- | ===== Backward Incompatible Changes ===== | + | <code php> |
+ | enum Suit { | ||
+ | case Hearts, Diamonds, Clubs, Spades; | ||
+ | } | ||
+ | </ | ||
+ | An Enumeration may have one or more '' | ||
- | " | + | Cases are not backed by a primitive value. That is, '' |
+ | <code php> | ||
+ | $a = Suit:: | ||
+ | $b = Suit:: | ||
- | ===== Proposed PHP Version(s) ===== | + | $a === $b; // true |
- | Next PHP 8.x. | ||
+ | $a instanceof Suit; // true | ||
+ | $a instanceof Suit:: | ||
+ | </ | ||
+ | ==== Enumerated Case Methods ==== | ||
- | ===== RFC Impact ===== | + | As both Enum Types and Enum Cases are implemented using classes, they may take methods. The Enum Type may also implement an interface, which all Cases must then fulfill, directly or indirectly. |
- | ===== Open Issues ===== | + | <code php> |
- | Make sure there are no open issues when the vote starts! | + | interface Colorful { |
+ | public function color(): string; | ||
+ | } | ||
- | ===== Unaffected PHP Functionality ===== | + | enum Suit implements Colorful { |
- | List existing areas/features of PHP that will not be changed by the RFC. | + | case Hearts { |
+ | public function color(): string { | ||
+ | return " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | case Diamonds { | ||
+ | public function color(): string { | ||
+ | return " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | case Clubs { | ||
+ | public function color(): string { | ||
+ | return " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | case Spades { | ||
+ | public function color(): string { | ||
+ | return " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | public function shape(): string { | ||
+ | return " | ||
+ | } | ||
+ | } | ||
- | This helps avoid any ambiguity, shows that you have thought deeply about the RFC's impact, and helps reduces mail list noise. | + | function paint(Colorful $c) { ... } |
- | ===== Future Scope ===== | + | paint(Suit:: |
- | This section details areas where the feature might be improved in future, but that are not currently proposed in this RFC. | + | </ |
+ | In this example, all four Enum cases will have a method '' | ||
- | ===== Proposed Voting Choices ===== | + | Enum Cases may not implement interfaces themselves. |
- | This is a simple yes/no vote to include Enumerations. 2/3 required to pass. | + | Static methods on Cases are not supported. Static methods on the Enum Type are supported. |
- | ===== Patches and Tests ===== | + | (Ilija: We haven’t discussed static methods at all. This is what makes the most sense to me at the moment but we can easily revisit this. I’m flexible.) |
- | Links to any external patches and tests go here. | + | |
- | If there is no patch, make it clear who will create | + | Inside |
- | Make it clear if the patch is intended to be the final patch, or is just a prototype. | + | (Note that in this case it would be a better data modeling practice to also define a '' |
- | For changes affecting | + | The above hierarchy is logically similar to the following class structure: |
- | ===== Implementation | + | <code php> |
- | After the project | + | interface Colorful { |
- | - the version(s) it was merged into | + | public function color(): string; |
- | - a link to the git commit(s) | + | } |
- | - a link to the PHP manual entry for the feature | + | |
- | - a link to the language | + | abstract class Suit implements Colorful { |
+ | public function shape(): string { | ||
+ | return " | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class Hearts extends Suit { | ||
+ | public function color(): string { | ||
+ | return " | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class Diamonds extends Suit { | ||
+ | public function color(): string { | ||
+ | return " | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class Clubs extends Suit { | ||
+ | public function color(): string { | ||
+ | return " | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class Spades extends Suit { | ||
+ | public function color(): string { | ||
+ | return " | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | ==== Value listing | ||
+ | |||
+ | The enumeration itself has an automatically generated static method '' | ||
+ | |||
+ | <code php> | ||
+ | Suit:: | ||
+ | // Produces: [Suit:: | ||
+ | </ | ||
+ | ==== Primitive-Equivalent Cases ==== | ||
+ | |||
+ | By default, Enumerated Cases have no primitive equivalent. They are simply singleton objects. However, there are ample cases where an Enumerated Case needs to be able to round-trip to a database or similar datastore, so having a built-in primitive (and thus trivially serializable) equivalent defined intrinsically | ||
+ | |||
+ | To define a primitive equivalent for an Enumeration, the syntax is as follows: | ||
+ | |||
+ | <code php> | ||
+ | enum Suit: string { | ||
+ | | ||
+ | case Diamonds = ' | ||
+ | case Clubs = ' | ||
+ | case Spades = ' | ||
+ | } | ||
+ | </ | ||
+ | Primitive backing types of '' | ||
+ | |||
+ | A Primitive-Equivalent Case will automatically down-cast to its primitive when used in a primitive context. For example, when used with '' | ||
+ | |||
+ | <code php> | ||
+ | print Suit:: | ||
+ | // prints " | ||
+ | print "I hope I draw a " . Suit:: | ||
+ | // prints "I hope I draw a S". | ||
+ | </ | ||
+ | Passing a Primitive Case to a primitive-typed parameter or return will produce | ||
+ | |||
+ | A Primitive-Backed enumeration also has a static method '' | ||
+ | |||
+ | <code php> | ||
+ | $record = get_stuff_from_database($id); | ||
+ | print $record[' | ||
+ | // Prints " | ||
+ | $suit = Suit:: | ||
+ | $suit === Suit:: | ||
+ | </ | ||
+ | A Primitive-Backed enumeration additionally has a method '' | ||
+ | |||
+ | <code php> | ||
+ | $list = Suit:: | ||
+ | $list === [ | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ]; // true | ||
+ | </ | ||
+ | Primitive-backed Cases are not allowed | ||
+ | |||
+ | <code php> | ||
+ | enum Suit: string { | ||
+ | case Hearts = ' | ||
+ | case Diamonds = ' | ||
+ | case Clubs = ' | ||
+ | case Spades = ' | ||
+ | public function color(): string { return ' | ||
+ | | ||
+ | |||
+ | public function color(): string | ||
+ | { | ||
+ | // ... | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | ==== Associated Values ==== | ||
+ | |||
+ | Enumerated Cases may optionally include associated values. An associated value is one that is associated with an instance of a Case. If a Case has associated values, it will **not** be implemented as a singleton. Each instance of the Case will then be its own object instance, so will not === another instance. | ||
+ | |||
+ | Associated values are mutually exclusive with Primitive-Equivalent Cases. | ||
+ | |||
+ | Associated values are defined using constructor property promotion. | ||
+ | |||
+ | <code php> | ||
+ | enum Distance { | ||
+ | case Kilometers(public int $num); | ||
+ | case Miles(public int $num); | ||
+ | } | ||
+ | |||
+ | $my_walk = Distance:: | ||
+ | // Named parameters work like any other function call. | ||
+ | $next_walk = Distance:: | ||
+ | |||
+ | print $my_walk-> | ||
+ | |||
+ | $my_walk === $next_walk; // FALSE! | ||
+ | </ | ||
+ | Enum Cases may not implement | ||
+ | |||
+ | An Enum Case that supports Associated Values is called an Associable Case. An Enum Case that does not have Associated Values is called a Unit Case. An Enumerated Type may consist of any combination of Associable and Unit Cases, but no Primitive-Equivalent Cases. | ||
+ | |||
+ | The Enum Type itself may not define associated values. Only a Case may do so. | ||
+ | |||
+ | Associated values are always read-only, both internally | ||
+ | |||
+ | On an Associable Case enumeration, | ||
+ | |||
+ | Use cases that would require more complete class functionality (arbitrary properties, custom constructors, | ||
+ | |||
+ | ==== Match expressions ==== | ||
+ | |||
+ | When dealing with Unit Cases, '' | ||
+ | |||
+ | <code php> | ||
+ | $val = Suit:: | ||
+ | |||
+ | $str = match ($val) { | ||
+ | Suit:: | ||
+ | Suit::Clubs => " | ||
+ | Suit:: | ||
+ | default => "The shape of my heart", | ||
+ | } | ||
+ | </ | ||
+ | That is not true when dealing with Associable Cases. Therefore, an alternate version of '' | ||
+ | |||
+ | <code php> | ||
+ | $val = Distance:: | ||
+ | |||
+ | $str = match type ($val) { | ||
+ | Distance:: | ||
+ | Distance:: | ||
+ | } | ||
+ | </ | ||
+ | (Ilija, your thoughts on this?) | ||
+ | |||
+ | ==== Examples ==== | ||
+ | |||
+ | Below are a few examples of Enums in action. | ||
+ | |||
+ | === Maybe === | ||
+ | |||
+ | The (in)famous Maybe Monad can be implemented like this: | ||
+ | |||
+ | <code php> | ||
+ | enum Maybe { | ||
+ | // This is a Unit Case. | ||
+ | case None { | ||
+ | public function bind(callable $f) { | ||
+ | return $this; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | // This is an Associable Case. | ||
+ | case Some(private mixed $value) { | ||
+ | // Note that the return type can be the Enum itself, thus restricting the return | ||
+ | // value to one of the enumerated types. | ||
+ | public function bind(callable $f) { | ||
+ | // $f is supposed to return a Maybe itself. | ||
+ | return $f($this-> | ||
+ | } | ||
+ | | ||
+ | |||
+ | // This method is available on both None and Some. | ||
+ | public function value(): mixed { | ||
+ | // Still need to sort out match() for this to make sense. | ||
+ | return match type ($this) { | ||
+ | Optional:: | ||
+ | Optional:: | ||
+ | }; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | === State machine === | ||
+ | |||
+ | Enums make it straightforward to express finite state machines. | ||
+ | |||
+ | <code php> | ||
+ | enum OvenStatus { | ||
+ | |||
+ | case Off { | ||
+ | public function turnOn() { return OvenStatus:: | ||
+ | }; | ||
+ | |||
+ | case On { | ||
+ | public function turnOff() { return OvenStatus:: | ||
+ | public function idle() { return OvenStatus:: | ||
+ | }; | ||
+ | |||
+ | case Idle { | ||
+ | public function on() { return OvenStatus:: | ||
+ | }; | ||
+ | } | ||
+ | </ | ||
+ | In this example, the oven can be in one of three states (Off, On, and Idling, meaning the flame is not on, but it will turn back on when it detects it needs to). However, it can never go from Off to Idle or Idle to Off; it must go through On state first. That means no tests need to be written or code paths defined for going from Off to Idle, because it’s literally impossible to even describe that state. | ||
+ | |||
+ | (Additional methods are of course likely in a real implementation.) | ||
+ | |||
+ | === Single Associable Enums === | ||
+ | |||
+ | Because all properties on an Enum are readonly, they offer a back-door way to create immutable objects. | ||
+ | |||
+ | <code php> | ||
+ | enum Point { | ||
+ | case ThreeD(public $x, public $x, public $z); | ||
+ | } | ||
+ | |||
+ | $p = Point:: | ||
+ | |||
+ | print $p->y; // prints 5 | ||
+ | $p->z = 9; // throws an Error of some kind, TBD. | ||
+ | </ | ||
+ | This is not a specific design goal of the implementation, | ||
+ | |||
+ | ===== Backward Incompatible Changes ===== | ||
+ | |||
+ | “enum” and “type” become | ||
+ | |||
+ | ===== Future Scope ===== | ||
+ | |||
+ | ==== Pattern matching ==== | ||
+ | |||
+ | Most languages that have an equivalent of associated values also support pattern matching as a way to extract values from the Enum Case. Pattern matching allows for a single '' | ||
+ | |||
+ | For now, matching against the Enum Case and accessing properties directly (something not supported in most ADT-supporting languages) is “good enough” and has mostly self-evident semantics based on existing PHP patterns. | ||
+ | |||
+ | ===== Voting ===== | ||
+ | |||
+ | This is a simple yes/no vote to include Enumerations. 2/3 required to pass. | ||
===== References ===== | ===== References ===== | ||
- | Links to external references, discussions or RFCs | ||
- | ===== Rejected Features ===== | + | [Survey of enumerations supported by various languages, conducted by Larry](https:// |
- | Keep this updated with features that were discussed on the mail lists. | + |
rfc/enumerations_and_adts.1600532071.txt.gz · Last modified: 2020/09/19 16:14 by crell