rfc:enumerations_and_adts
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionLast revisionBoth sides next revision | ||
rfc:enumerations_and_adts [2020/09/19 21:54] – crell | rfc:enumerations_and_adts [2020/11/13 23:33] – Updates from GitHub 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: | ||
===== 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 '' | + | The most popular case of enumerations is '' |
===== Proposal ===== | ===== Proposal ===== | ||
Line 21: | Line 21: | ||
==== Basic enumerations ==== | ==== Basic enumerations ==== | ||
- | This RFC introduces a new language construct, '' | + | This RFC introduces a new language construct, '' |
<code php> | <code php> | ||
Line 31: | Line 31: | ||
} | } | ||
</ | </ | ||
- | + | This declaration creates a new enumerated type named '' | |
- | This declaration creates a new enumerated type named %%Suit%%, which has four and only four legal values: '' | + | |
<code php> | <code php> | ||
Line 39: | Line 38: | ||
function pick_a_card(Suit $suit) { ... } | function pick_a_card(Suit $suit) { ... } | ||
- | pick_a_card($val); | + | pick_a_card($val); |
- | pick_a_card(Suit: | + | pick_a_card(Suit::Clubs); // OK |
- | pick_a_card(' | + | pick_a_card(' |
</ | </ | ||
- | + | In the simple case, multiple cases may be defined on a single line. The following is semantically equivalent to the definition above. | |
- | In the simple case, multiple cases may be defined on a single line. The following is semantically equivalent to the definition above. | + | |
<code php> | <code php> | ||
Line 51: | Line 49: | ||
} | } | ||
</ | </ | ||
- | |||
An Enumeration may have one or more '' | An Enumeration may have one or more '' | ||
- | Cases are not backed by a primitive value. | + | Cases are not backed by a primitive value. That is, '' |
<code php> | <code php> | ||
Line 66: | Line 63: | ||
$a instanceof Suit:: | $a instanceof Suit:: | ||
</ | </ | ||
- | |||
- | [Note to Iliya: The last line there is the tricksy one we haven' | ||
- | |||
- | Each Case class includes a default '' | ||
- | |||
- | <code php> | ||
- | print Suit:: | ||
- | // prints " | ||
- | </ | ||
- | |||
- | That function may be overridden if desired. | ||
- | |||
- | [To Ilija: Do we want this part or not? I only thought of it while writing this. I don't know if it's good or bad.] | ||
- | |||
- | Enumerated type Cases may be used in union type definitions. | ||
- | |||
- | <code php> | ||
- | function gimmie_red_card(Suit:: | ||
- | </ | ||
- | |||
==== Enumerated Case Methods ==== | ==== Enumerated Case Methods ==== | ||
- | As both Enum Types and Enum Cases are implemented using classes, they may take methods. | + | 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. |
<code php> | <code php> | ||
Line 129: | Line 106: | ||
paint(Suit:: | paint(Suit:: | ||
</ | </ | ||
- | + | In this example, all four Enum cases will have a method '' | |
- | In this example, all four Enum cases will have a method '' | + | |
Enum Cases may not implement interfaces themselves. | Enum Cases may not implement interfaces themselves. | ||
- | Static methods on Cases are not supported. | + | Static methods on Cases are not supported. Static methods on the Enum Type are supported. |
- | [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.) | + | (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.) |
- | Inside a method on a Case, The '' | + | Inside a method on a Case, The '' |
- | (Note that in this case it would be a better data modeling practice to also define a '' | + | (Note that in this case it would be a better data modeling practice to also define a '' |
The above hierarchy is logically similar to the following class structure: | The above hierarchy is logically similar to the following class structure: | ||
Line 179: | Line 155: | ||
} | } | ||
</ | </ | ||
+ | ==== 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 is useful. | ||
+ | |||
+ | To define a primitive equivalent for an Enumeration, | ||
+ | |||
+ | <code php> | ||
+ | enum Suit: string { | ||
+ | case Hearts = ' | ||
+ | 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 the primitive value in weak-typing mode, and produce a '' | ||
+ | |||
+ | 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 to define a '' | ||
+ | |||
+ | <code php> | ||
+ | enum Suit: string { | ||
+ | case Hearts = ' | ||
+ | case Diamonds = ' | ||
+ | case Clubs = ' | ||
+ | case Spades = ' | ||
+ | public function color(): string { return ' | ||
+ | } | ||
+ | |||
+ | public function color(): string | ||
+ | { | ||
+ | // ... | ||
+ | } | ||
+ | } | ||
+ | </ | ||
==== Associated Values ==== | ==== Associated Values ==== | ||
- | Enumerated Cases may optionally include 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. | Associated values are defined using constructor property promotion. | ||
Line 200: | Line 248: | ||
$my_walk === $next_walk; // FALSE! | $my_walk === $next_walk; // FALSE! | ||
</ | </ | ||
+ | Enum Cases may not implement a full constructor. However, they may list parameters that will be auto-promoted to properties using constructor promotion. The visibility modifier is required. Cases may not implement properties other than promoted properties. | ||
- | 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 |
- | An Enum Case that supports Associated Values is called an Associable Case. An Enum Case that does not have Associated Values is called | + | The Enum Type itself may not define associated values. Only a Case may do so. |
- | The Enum Type itself may not define associated | + | Associated |
- | Associated values are always read-only, both internally to the class and externally. | + | On an Associable Case enumeration, the '' |
Use cases that would require more complete class functionality (arbitrary properties, custom constructors, | Use cases that would require more complete class functionality (arbitrary properties, custom constructors, | ||
Line 213: | Line 262: | ||
==== Match expressions ==== | ==== Match expressions ==== | ||
- | When dealing with Unit Cases, '' | + | When dealing with Unit Cases, '' |
<code php> | <code php> | ||
Line 225: | Line 274: | ||
} | } | ||
</ | </ | ||
- | + | That is not true when dealing with Associable Cases. Therefore, an alternate version of '' | |
- | That is not true when dealing with Associable Cases. | + | |
<code php> | <code php> | ||
Line 236: | Line 284: | ||
} | } | ||
</ | </ | ||
- | + | (Ilija, your thoughts on this?) | |
- | [Ilija, your thoughts on this?] | + | |
==== Examples ==== | ==== Examples ==== | ||
Line 255: | Line 302: | ||
} | } | ||
}; | }; | ||
- | | + | |
// This is an Associable Case. | // This is an Associable Case. | ||
- | case Some { | + | case Some(private mixed $value) |
// Note that the return type can be the Enum itself, thus restricting the return | // Note that the return type can be the Enum itself, thus restricting the return | ||
// value to one of the enumerated types. | // value to one of the enumerated types. | ||
Line 269: | Line 316: | ||
public function value(): mixed { | public function value(): mixed { | ||
// Still need to sort out match() for this to make sense. | // Still need to sort out match() for this to make sense. | ||
- | return match enum ($this) { | + | return match type ($this) { |
Optional:: | Optional:: | ||
Optional:: | Optional:: | ||
Line 276: | Line 323: | ||
} | } | ||
</ | </ | ||
- | + | === State machine === | |
- | === State machine | + | |
Enums make it straightforward to express finite state machines. | Enums make it straightforward to express finite state machines. | ||
+ | <code php> | ||
enum OvenStatus { | enum OvenStatus { | ||
Line 296: | Line 343: | ||
}; | }; | ||
} | } | ||
- | + | </ | |
- | 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. | + | 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.) | (Additional methods are of course likely in a real implementation.) | ||
Line 315: | Line 362: | ||
$p->z = 9; // throws an Error of some kind, TBD. | $p->z = 9; // throws an Error of some kind, TBD. | ||
</ | </ | ||
- | |||
This is not a specific design goal of the implementation, | This is not a specific design goal of the implementation, | ||
- | |||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
- | "enum" becomes a language | + | “enum” and “type” become |
- | + | ||
- | + | ||
- | ===== Proposed PHP Version(s) ===== | + | |
- | + | ||
- | Next PHP 8.x. | + | |
- | + | ||
- | + | ||
- | ===== RFC Impact ===== | + | |
- | + | ||
- | ===== Open Issues ===== | + | |
- | + | ||
- | We're still not sure what to do with '' | + | |
- | + | ||
- | Details of how the object-ness of Enum Cases get exposed are still unclear. | + | |
- | + | ||
- | ===== Unaffected PHP Functionality ===== | + | |
- | + | ||
- | No existing functionality should be affected, other than the new " | + | |
===== Future Scope ===== | ===== Future Scope ===== | ||
- | |||
- | ==== Case enumeration ==== | ||
- | |||
- | In some languages, it is possible to enumerate all possible values of an Enum Type. For now that functionality is not implemented, | ||
==== Pattern matching ==== | ==== 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 '' | + | 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" | + | |
- | + | ||
- | ===== Proposed Voting Choices ===== | + | |
- | + | ||
- | This is a simple yes/no vote to include Enumerations. | + | |
- | + | ||
- | ===== Patches and Tests ===== | + | |
- | Links to any external patches and tests go here. | + | |
- | + | ||
- | If there is no patch, make it clear who will create a patch, or whether a volunteer to help with implementation is needed. | + | |
- | Make it clear if the patch is intended to be the final patch, or is just a prototype. | + | For now, matching against |
- | For changes affecting the core language, you should also provide a patch for the language specification. | + | ===== Voting ===== |
- | ===== Implementation ===== | + | This is a simple yes/no vote to include Enumerations. 2/3 required |
- | After the project | + | |
- | - the version(s) it was merged into | + | |
- | - a link to the git commit(s) | + | |
- | - a link to the PHP manual entry for the feature | + | |
- | - a link to the language specification section (if any) | + | |
===== References ===== | ===== References ===== | ||
- | [[https:// | + | [Survey of enumerations supported by various languages, conducted by Larry](https:// |
- | ===== Rejected Features ===== | ||
- | Keep this updated with features that were discussed on the mail lists. |
rfc/enumerations_and_adts.txt · Last modified: 2020/12/04 23:26 by crell