[RFC] Enumerations, Round 2

php.internals

Larry Garfield

5 years ago
Hello, Internalians! After considerable discussion and effort, Ilija and I are ready to offer you round 2 on enumerations. This is in the spirit of the previous discussion, but based on that discussion a great deal has been reworked. The main change is that Enumeration Cases are now object instances of the Enumeration class rather than their own class. Most of the other changes are knock-on effects of that. Of particular note: * Cases may not have methods or constants on them. They're just dumb values. * Enums themselves may have methods, static methods, or constants. * Traits are supported, as long as they don't have properties. * The value() method on scalar enums is now a property. The full RFC is here, and I recommend reading it again in full given how much was updated. https://wiki.php.net/rfc/enumerations The implementation is 98% complete; there's still a few lagging bits in reflection, and some opcache bugs that Ilija is still stomping on. There are a few outstanding questions listed that we would like feedback on. We're not entirely certain which direction to go with them, for reasons explained in the RFC. Input on those is especially welcome. Happy New Year. May it be enumerable.
-- Larry Garfield larry@garfieldtech.com

Benjamin Morel

5 years ago
Hi Larry, thank you for the updated RFC! I love it, and having played with the implementation, I can say I love it so far as well. I have one suggestion regarding reflection: Shouldn't ReflectionCase expose an additional getInstance() method, that would return the case instance, such as Suit::HEARTS? Also, I noticed 2 typos in the code samples: - in "Enumerated Methods", 1st code block: missing "implements Colorful" after "enum Suit" - in "Enumerated Methods", 2nd code block: missing "public function color()" after "private function __construct() {}" Finally, I got a segmentation fault while trying to use what I think is an unsupported syntax (removing the ":string" from a scalar enum), where is the correct place to report this? The GitHub PR? Good luck with the RFC! - Benjamin On Mon, 28 Dec 2020 at 21:22, Larry Garfield <larry@garfieldtech.com> wrote:

Larry Garfield

5 years ago
On Mon, Dec 28, 2020, at 6:40 PM, Benjamin Morel wrote:
> Hi Larry, thank you for the updated RFC! > I love it, and having played with the implementation, I can say I love it > so far as well. > > I have one suggestion regarding reflection: > Shouldn't ReflectionCase expose an additional getInstance() method, that > would return the case instance, such as Suit::HEARTS?
I... thought we had that in there. Did I remove the wrong method? I know it's in there somewhere. Let me double check with Ilija on what it's supposed to be called. Will update when that's sorted out.
> Also, I noticed 2 typos in the code samples: > > - in "Enumerated Methods", 1st code block: missing "implements Colorful" > after "enum Suit" > - in "Enumerated Methods", 2nd code block: missing "public function > color()" after "private function __construct() {}"
Both fixed, thanks.
> Finally, I got a segmentation fault while trying to use what I think is an > unsupported syntax (removing the ":string" from a scalar enum), where is > the correct place to report this? The GitHub PR?
On the PR, please. Sounds like we need a few more negative tests to make sure it fails gracefully. --Larry Garfield

Marc

5 years ago
On 28.12.20 21:21, Larry Garfield wrote:
> Hello, Internalians! > > After considerable discussion and effort, Ilija and I are ready to offer you round 2 on enumerations. This is in the spirit of the previous discussion, but based on that discussion a great deal has been reworked. The main change is that Enumeration Cases are now object instances of the Enumeration class rather than their own class. Most of the other changes are knock-on effects of that. > > Of particular note: > > * Cases may not have methods or constants on them. They're just dumb values. > * Enums themselves may have methods, static methods, or constants. > * Traits are supported, as long as they don't have properties. > * The value() method on scalar enums is now a property. > > The full RFC is here, and I recommend reading it again in full given how much was updated.
I did and the RFC looks really awesome :+1: I don't have time to test the implementation but I noticed one thing:
> If the enumeration is not a Scalar Enum, the array will be packed
(indexed sequentially starting from 0). If the enumeration is a Scalar Enum, the keys will be the corresponding scalar for each enumeration. I don't think using the scalar values as keys is a good idea. What happens if we want to support scalar float values? (Why are they actually not supported in the first place?) Also I think it's more natural if both enum types return a zero-indexed-array of cases.

Larry Garfield

5 years ago
On Tue, Dec 29, 2020, at 2:48 AM, Marc wrote:
> > On 28.12.20 21:21, Larry Garfield wrote: > > Hello, Internalians! > > > > After considerable discussion and effort, Ilija and I are ready to offer you round 2 on enumerations. This is in the spirit of the previous discussion, but based on that discussion a great deal has been reworked. The main change is that Enumeration Cases are now object instances of the Enumeration class rather than their own class. Most of the other changes are knock-on effects of that. > > > > Of particular note: > > > > * Cases may not have methods or constants on them. They're just dumb values. > > * Enums themselves may have methods, static methods, or constants. > > * Traits are supported, as long as they don't have properties. > > * The value() method on scalar enums is now a property. > > > > The full RFC is here, and I recommend reading it again in full given how much was updated. > > I did and the RFC looks really awesome :+1: > > I don't have time to test the implementation but I noticed one thing: > > > If the enumeration is not a Scalar Enum, the array will be packed > (indexed sequentially starting from 0). If the enumeration is a Scalar > Enum, the keys will be the corresponding scalar for each enumeration. > > I don't think using the scalar values as keys is a good idea. What > happens if we want to support scalar float values? (Why are they > actually not supported in the first place?)
That's why floats are not supported, in fact, because what happens to them when they are made into an array key is non-obvious. (PHP would say to convert to a string, but that's always fussy with possible data loss in some cases, etc.) We decided to just avoid that problem until/unless someone found a good use case for float enums. No all languages support them as is, so there is precedent.
> Also I think it's more natural if both enum types return a > zero-indexed-array of cases.
The goal is to make it easy to work with them, and having a clean lookup map readily available is very convenient. If you don't care about the scalar equivalent then you can safely ignore them. If you do want them, then you have a lookup table ready-made for you. That's the logic we were working from. --Larry Garfield

Marc

5 years ago
On 29.12.20 16:42, Larry Garfield wrote:
> On Tue, Dec 29, 2020, at 2:48 AM, Marc wrote: >> On 28.12.20 21:21, Larry Garfield wrote: >>> Hello, Internalians! >>> >>> After considerable discussion and effort, Ilija and I are ready to offer you round 2 on enumerations. This is in the spirit of the previous discussion, but based on that discussion a great deal has been reworked. The main change is that Enumeration Cases are now object instances of the Enumeration class rather than their own class. Most of the other changes are knock-on effects of that. >>> >>> Of particular note: >>> >>> * Cases may not have methods or constants on them. They're just dumb values. >>> * Enums themselves may have methods, static methods, or constants. >>> * Traits are supported, as long as they don't have properties. >>> * The value() method on scalar enums is now a property. >>> >>> The full RFC is here, and I recommend reading it again in full given how much was updated. >> I did and the RFC looks really awesome :+1: >> >> I don't have time to test the implementation but I noticed one thing: >> >>> If the enumeration is not a Scalar Enum, the array will be packed >> (indexed sequentially starting from 0). If the enumeration is a Scalar >> Enum, the keys will be the corresponding scalar for each enumeration. >> >> I don't think using the scalar values as keys is a good idea. What >> happens if we want to support scalar float values? (Why are they >> actually not supported in the first place?) > That's why floats are not supported, in fact, because what happens to them when they are made into an array key is non-obvious. (PHP would say to convert to a string, but that's always fussy with possible data loss in some cases, etc.) We decided to just avoid that problem until/unless someone found a good use case for float enums. No all languages support them as is, so there is precedent. > >> Also I think it's more natural if both enum types return a >> zero-indexed-array of cases. > The goal is to make it easy to work with them, and having a clean lookup map readily available is very convenient. If you don't care about the scalar equivalent then you can safely ignore them. If you do want them, then you have a lookup table ready-made for you. That's the logic we were working from.
I don't have a really good use-case for float values. It just seems weird to me that a ScalarEnum doesn't support all scalars. Using the enum value as array key for `cases()` works with your current proposal but if we later want to allow floats, bool whatever then we got a food gun. You already provide a lookup mechanism with `MyEnum::from()` - I don't see a real use-case for proving a pre build map. The main use case I see is to list all possible enum values but this doesn't require a map and a zero-indexed-array would also be more performant with packed arrays (correct me if I'm wrong).

Marc

5 years ago
On 03.01.21 11:56, Marc wrote:
> On 29.12.20 16:42, Larry Garfield wrote: >> On Tue, Dec 29, 2020, at 2:48 AM, Marc wrote: >>> On 28.12.20 21:21, Larry Garfield wrote: >>>> Hello, Internalians! >>>> >>>> After considerable discussion and effort, Ilija and I are ready to offer you round 2 on enumerations. This is in the spirit of the previous discussion, but based on that discussion a great deal has been reworked. The main change is that Enumeration Cases are now object instances of the Enumeration class rather than their own class. Most of the other changes are knock-on effects of that. >>>> >>>> Of particular note: >>>> >>>> * Cases may not have methods or constants on them. They're just dumb values. >>>> * Enums themselves may have methods, static methods, or constants. >>>> * Traits are supported, as long as they don't have properties. >>>> * The value() method on scalar enums is now a property. >>>> >>>> The full RFC is here, and I recommend reading it again in full given how much was updated. >>> I did and the RFC looks really awesome :+1: >>> >>> I don't have time to test the implementation but I noticed one thing: >>> >>>> If the enumeration is not a Scalar Enum, the array will be packed >>> (indexed sequentially starting from 0). If the enumeration is a Scalar >>> Enum, the keys will be the corresponding scalar for each enumeration. >>> >>> I don't think using the scalar values as keys is a good idea. What >>> happens if we want to support scalar float values? (Why are they >>> actually not supported in the first place?) >> That's why floats are not supported, in fact, because what happens to them when they are made into an array key is non-obvious. (PHP would say to convert to a string, but that's always fussy with possible data loss in some cases, etc.) We decided to just avoid that problem until/unless someone found a good use case for float enums. No all languages support them as is, so there is precedent. >> >>> Also I think it's more natural if both enum types return a >>> zero-indexed-array of cases. >> The goal is to make it easy to work with them, and having a clean lookup map readily available is very convenient. If you don't care about the scalar equivalent then you can safely ignore them. If you do want them, then you have a lookup table ready-made for you. That's the logic we were working from. > I don't have a really good use-case for float values. It just seems > weird to me that a ScalarEnum doesn't support all scalars. > > Using the enum value as array key for `cases()` works with your current > proposal but if we later want to allow floats, bool whatever then we got > a food gun.
Forgot to mention on (virtiually) adding generics to the game the method `cases(): array;`<http://www.php.net/array>would be described as `cases(): array<int, static>;` on UnitEnum but `cases(): array<string|int, static>;` on ScalarEnum which is not compatible for reasons and I think (even if not yet possible with PHP) such things needs to be considered on producing clean interfaces.
> > You already provide a lookup mechanism with `MyEnum::from()` - I don't > see a real use-case for proving a pre build map. The main use case I see > is to list all possible enum values but this doesn't require a map and a > zero-indexed-array would also be more performant with packed arrays > (correct me if I'm wrong). >
Thanks, Marc

Ilija Tovilo

5 years ago
Hi Marc
> I don't have a really good use-case for float values. It just seems > weird to me that a ScalarEnum doesn't support all scalars. > > Using the enum value as array key for `cases()` works with your current > proposal but if we later want to allow floats, bool whatever then we got > a food gun.
The main reason is that we're using a hashmap internally in from() to find the given case you're looking for. This is the same hashmap PHP arrays are based on which only supports ints/strings as keys. If we were to allow any scalar as a value, looking up a case by value would become a O(n) operation. We could do something terrible like serialize the key before storing it in the hashmap to allow arbitrary key types. But that will require serializing the value on each invocation of from() which will unnecessarily slow down the 95% most common use cases (int/string) to support the exception. Note though that it's always easier to extend than to remove. By not offering this feature we're erring on the side of caution. That being said, I can see how ScalarEnum is a misleading name. We've been thinking about a better name and only had some ideas we weren't fully satisfied with. RawEnum, ValueEnum and ConvertibleEnum were some of these ideas. Let us know if you have a better suggestion.
> You already provide a lookup mechanism with `MyEnum::from()` - I don't > see a real use-case for proving a pre build map. The main use case I see > is to list all possible enum values but this doesn't require a map and a > zero-indexed-array would also be more performant with packed arrays > (correct me if I'm wrong).
I do somewhat agree with you there. We're essentially returning `Array<UnitEnum>|Map<int|string, ScalarEnum>` which feels inconsistent. When you're calling cases() you're most likely going to loop over it at which point $case->value is available at your disposal. Ilija

Marc

5 years ago
Hi Ilija, On 03.01.21 12:54, Ilija Tovilo wrote:
> Hi Marc > >> I don't have a really good use-case for float values. It just seems >> weird to me that a ScalarEnum doesn't support all scalars. >> >> Using the enum value as array key for `cases()` works with your current >> proposal but if we later want to allow floats, bool whatever then we got >> a food gun. > The main reason is that we're using a hashmap internally in from() to > find the given case you're looking for. This is the same hashmap PHP > arrays are based on which only supports ints/strings as keys. If we > were to allow any scalar as a value, looking up a case by value would > become a O(n) operation.w > > We could do something terrible like serialize the key before storing > it in the hashmap to allow arbitrary key types. But that will require > serializing the value on each invocation of from() which will > unnecessarily slow down the 95% most common use cases (int/string) to > support the exception. Note though that it's always easier to extend > than to remove. By not offering this feature we're erring on the side > of caution. > > That being said, I can see how ScalarEnum is a misleading name. We've > been thinking about a better name and only had some ideas we weren't > fully satisfied with. RawEnum, ValueEnum and ConvertibleEnum were some > of these ideas. Let us know if you have a better suggestion.
That's reasonable and makes sense at this point. Only supporting string and int is also fine (I don't have a personal use case for other types). So using the same HT implementation as arrays internally totally makes sense but this is an implementation detail not visible for the outside and we shouldn't block outself for the future now as nobody knows of unknown possible use cases. At least if we can avoid it.
>> You already provide a lookup mechanism with `MyEnum::from()` - I don't >> see a real use-case for proving a pre build map. The main use case I see >> is to list all possible enum values but this doesn't require a map and a >> zero-indexed-array would also be more performant with packed arrays >> (correct me if I'm wrong). > I do somewhat agree with you there. We're essentially returning > `Array<UnitEnum>|Map<int|string, ScalarEnum>` which feels > inconsistent. When you're calling cases() you're most likely going to > loop over it at which point $case->value is available at your > disposal.
Would you consider making `cases()` returning a simple list in all cases instead of differentiate between UnitEnum and ScalarEnum given the fact that mostly people just want to loop over cases and a lookup is already available with ScalarEnum::from() to provide a cleaner interface? Marc

Larry Garfield

5 years ago
On Sun, Jan 3, 2021, at 2:25 PM, Marc wrote:
> >> You already provide a lookup mechanism with `MyEnum::from()` - I don't > >> see a real use-case for proving a pre build map. The main use case I see > >> is to list all possible enum values but this doesn't require a map and a > >> zero-indexed-array would also be more performant with packed arrays > >> (correct me if I'm wrong). > > I do somewhat agree with you there. We're essentially returning > > `Array<UnitEnum>|Map<int|string, ScalarEnum>` which feels > > inconsistent. When you're calling cases() you're most likely going to > > loop over it at which point $case->value is available at your > > disposal. > > Would you consider making `cases()` returning a simple list in all cases > instead of differentiate between UnitEnum and ScalarEnum given the fact > that mostly people just want to loop over cases and a lookup is already > available with ScalarEnum::from() to provide a cleaner interface? > > Marc
Ilija and I talked this one over a bit more, and decided that you're right. Between ->value and from() we couldn't come up with a use case that would need the assoc array that wouldn't work just as well with ->value, and it makes the method type definition simpler. I've updated the RFC to have cases() always return a packed array; Ilija will update the PR soon. Thanks for your feedback! --Larry Garfield

Markus Fischer

5 years ago
Hi, On 28.12.20 21:21, Larry Garfield wrote:
> The full RFC is here, and I recommend reading it again in full given how much was updated. > > https://wiki.php.net/rfc/enumerations
I tried to answer the following question but failed to do so: What is the scalar value for a ScalarEnum if none is explicitly defined? The RFC makes this example:
> enum Suit: string implements Colorful { > case Hearts = 'H'; > case Diamonds = 'D';
> 'D' == Suit::Diamonds->value; // true
What in this case?
> enum Suit: string { > case Hearts; > case Diamonds;
… What is the outcome of `Suit::Diamonds->value` ? Thanks! - Markus

Rowan Collins

5 years ago
On 30 December 2020 08:43:33 GMT+00:00, Markus Fischer <markus@fischer.name> wrote:
>What is the scalar value for a ScalarEnum if none is explicitly >defined?
The question has no answer, because the declaration of the enum itself would be invalid:
> If an enumeration is marked as having a scalar equivalent, then all cases must have a unique scalar equivalent defined explicitly.
enum Suit: string { case Hearts; case Diamonds; } Presumably, this would result in an Error being thrown when compiling the declaration. Regards,
-- Rowan Tommins [IMSoP]

Markus Fischer

5 years ago
On 30.12.20 12:00, Rowan Tommins wrote:
> On 30 December 2020 08:43:33 GMT+00:00, Markus Fischer <markus@fischer.name> wrote: >> What is the scalar value for a ScalarEnum if none is explicitly >> defined? > > The question has no answer, because the declaration of the enum itself would be invalid: > >> If an enumeration is marked as having a scalar equivalent, then all cases must have a unique scalar equivalent defined explicitly. > > enum Suit: string { > case Hearts; > case Diamonds; > } > > Presumably, this would result in an Error being thrown when compiling the declaration.
I see. What's the quickest way (=less code) to have an enum represent it's lexical name as the literal values? So that `… case Foo; case Bar; …` results in `::Foo->value === 'Foo'` etc.? Is this possible without implementing this manually for all cases? thanks, - Markus

Benjamin Morel

5 years ago
> > What's the quickest way (=less code) to have an enum represent it's > lexical name as the literal values? > > So that `… case Foo; case Bar; …` results in `::Foo->value === 'Foo'` etc.? > > Is this possible without implementing this manually for all cases? > >
AFAICS, you'd need to implement the values manually. The $value property is only defined on ScalarEnum, not UnitEnum. - Benjamin

Larry Garfield

5 years ago
On Wed, Dec 30, 2020, at 2:43 AM, Markus Fischer wrote:
> Hi, > > On 28.12.20 21:21, Larry Garfield wrote: > > The full RFC is here, and I recommend reading it again in full given how much was updated. > > > > https://wiki.php.net/rfc/enumerations > > I tried to answer the following question but failed to do so: > > What is the scalar value for a ScalarEnum if none is explicitly defined? > > The RFC makes this example: > > enum Suit: string implements Colorful { > > case Hearts = 'H'; > > case Diamonds = 'D'; > … > > 'D' == Suit::Diamonds->value; // true > > > What in this case? > > > enum Suit: string { > > case Hearts; > > case Diamonds; > … > > What is the outcome of `Suit::Diamonds->value` ?
A parse error on the declaration; if it's a scalar enum, explicit values are required for all cases. --Larry Garfield

Alexandru Pătrănescu

5 years ago
On Mon, Dec 28, 2020 at 10:22 PM Larry Garfield <larry@garfieldtech.com> wrote:
> > Hello, Internalians! > > After considerable discussion and effort, Ilija and I are ready to offer
you round 2 on enumerations. This is in the spirit of the previous discussion, but based on that discussion a great deal has been reworked. The main change is that Enumeration Cases are now object instances of the Enumeration class rather than their own class. Most of the other changes are knock-on effects of that.
> > Of particular note: > > * Cases may not have methods or constants on them. They're just dumb
values.
> * Enums themselves may have methods, static methods, or constants. > * Traits are supported, as long as they don't have properties. > * The value() method on scalar enums is now a property. > > The full RFC is here, and I recommend reading it again in full given how
much was updated.
> > https://wiki.php.net/rfc/enumerations > > The implementation is 98% complete; there's still a few lagging bits in
reflection, and some opcache bugs that Ilija is still stomping on.
> > There are a few outstanding questions listed that we would like feedback
on. We're not entirely certain which direction to go with them, for reasons explained in the RFC. Input on those is especially welcome.
> > Happy New Year. May it be enumerable. > > -- > Larry Garfield > larry@garfieldtech.com > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php >
Hi! Nice evolution overall. Few notes: - I think ScalarEnum::fromValue() would be more clear than just from() and more in sync with ->value property. - For the class structure you gave an example to illustrate the 'similar' way it is related to class, it would be nice to also mark the class as final. - I agree with no state behavior, at least at this point. However, I would have liked to offer the ability for very easy creation of singleton/multiton patterns using enums, like in Java. - Enums evolve in time and cases are added or sometimes removed. When the storage of the case stays externally (as scalar values or serialized), there could be issues when some of them are removed in the meantime. - for ScalarEnum::from(), for missing values I would guess we will throw an exception, maybe a specific one? Can we mention it in the RFC? - for deserialization, how should it be handled? using an exception as well, same exception as in the previous case. - The allowed_classes option on unserialize() method, I'm guessing it will work with Enums just like every other class, putting an instance of __PHP_Incomplete_Class? - Inheritance would work between enums. But only by eliminating some cases. I can see how enum RedSuites could extend enum Suites without breaking LSP. Not sure if it makes sense to dig on this now. Regards, Alex

Larry Garfield

5 years ago
On Wed, Dec 30, 2020, at 4:12 AM, Alexandru Pătrănescu wrote:
> Nice evolution overall. > > Few notes: > - I think ScalarEnum::fromValue() would be more clear than just from() and > more in sync with ->value property.
Any name would work, I suppose. I prefer short, single-word methods where possible, myself.
> - For the class structure you gave an example to illustrate the 'similar' > way it is related to class, it would be nice to also mark the class as > final.
It would. Added.
> - I agree with no state behavior, at least at this point. However, I would > have liked to offer the ability for very easy creation of > singleton/multiton patterns using enums, like in Java.
I'm not sure what you're describing here. Do you mean tagged unions/ADTs, which are punted to a later RFC? https://wiki.php.net/rfc/tagged_unions
> - Enums evolve in time and cases are added or sometimes removed. When the > storage of the case stays externally (as scalar values or serialized), > there could be issues when some of them are removed in the meantime.
Possibly; though that's no different than a class evolving or going away in time. Either way it's up to the developer to figure out what BC strategy makes sense.
> - for ScalarEnum::from(), for missing values I would guess we will throw > an exception, maybe a specific one? Can we mention it in the RFC?
It already says a ValueError is thrown. It probably does make sense to subclass that, though. I'll mention it to Ilija.
> - for deserialization, how should it be handled? using an exception as > well, same exception as in the previous case.
Ilija and I discussed this a bit, and settled on an invalid deserialiation issuing a warning and returning false. An exception would probably be better, but right now deserialize() doesn't throw things so introducing that just for enums seems inconsistent. He'll be updating the implementation shortly.
> - The allowed_classes option on unserialize() method, I'm guessing it will > work with Enums just like every other class, putting an instance of > __PHP_Incomplete_Class?
Enums don't have unsafe behavior in their constructors the way classes do, so we're not convinced there's a reason to exclude them. For now allowed_classes does not include enums.
> - Inheritance would work between enums. But only by eliminating some cases. > I can see how enum RedSuites could extend enum Suites without breaking LSP. > Not sure if it makes sense to dig on this now. > > Regards, > Alex
Possibly as a future scope, if we see demand for it. The RFC is ambitious enough as is and already has several follow-ups planned for more advanced cases. :-) Cheers! --Larry Garfield

Rowan Collins

5 years ago
On 28/12/2020 20:21, Larry Garfield wrote:
> After considerable discussion and effort, Ilija and I are ready to offer you round 2 on enumerations.
Thank you both, again, for all your efforts. I'm pleased to say that I like this draft even more than the last one. :) A couple of points that occurred to me reading through: - The magic methods section lists __call as allowed, but not __callStatic; was this deliberate, or just an oversight? - Under Future Scope, the "Grouped Syntax" sub-section says "That would only work on the simple, non-primitive-backed case with no methods defined [...] it is unclear how common that will be in practice" This caveat doesn't apply to the current proposed syntax, and should perhaps be re-visited. Given that this is currently a legal declaration: class Suit {   const Hearts = 'H', Diamonds = 'D', Clubs = 'C', Spades = 'S'; } It seems fairly reasonable for the enum version to allow the same syntax: enum Suit: string {   case Hearts = 'H', Diamonds = 'D', Clubs = 'C', Spades = 'S'; } Or, for a non-scalar enum: enum Suit {   case Hearts, Diamonds, Clubs, Spades; } Regards,
-- Rowan Tommins [IMSoP]

Larry Garfield

5 years ago
On Wed, Dec 30, 2020, at 6:27 AM, Rowan Tommins wrote:
> On 28/12/2020 20:21, Larry Garfield wrote: > > After considerable discussion and effort, Ilija and I are ready to offer you round 2 on enumerations. > > > Thank you both, again, for all your efforts. I'm pleased to say that I > like this draft even more than the last one. :) > > A couple of points that occurred to me reading through: > > - The magic methods section lists __call as allowed, but not > __callStatic; was this deliberate, or just an oversight?
More oversight; the reasons for not including it initially aren't really good reasons, so we'll unblock that.
> - Under Future Scope, the "Grouped Syntax" sub-section says "That would > only work on the simple, non-primitive-backed case with no methods > defined [...] it is unclear how common that will be in practice" This > caveat doesn't apply to the current proposed syntax, and should perhaps > be re-visited. > > Given that this is currently a legal declaration: > > class Suit { >   const Hearts = 'H', Diamonds = 'D', Clubs = 'C', Spades = 'S'; > } > > It seems fairly reasonable for the enum version to allow the same syntax: > > enum Suit: string { >   case Hearts = 'H', Diamonds = 'D', Clubs = 'C', Spades = 'S'; > } > > Or, for a non-scalar enum: > > enum Suit { >   case Hearts, Diamonds, Clubs, Spades; > }
That's partially left over from when we had per-case methods, where grouping would be highly fugly. However, I'm still advocating for tagged unions (in a future step) having per-case methods, and that would make the grouping syntax fugly again. It's trivial to add later, so for now I think it's best to skip. If we find later on it doesn't get in the way of anything it's an easy addition. --Larry Garfield

Rowan Collins

5 years ago
On 30/12/2020 19:47, Larry Garfield wrote:
> That's partially left over from when we had per-case methods, where grouping would be highly fugly. However, I'm still advocating for tagged unions (in a future step) having per-case methods, and that would make the grouping syntax fugly again. It's trivial to add later, so for now I think it's best to skip. If we find later on it doesn't get in the way of anything it's an easy addition.
Yes, that had occurred to me. Just to reiterate, though, the original justification read "it is unclear how common [cases without block definitions] will be in practice"; with the current implementation, we know for a fact that it will be extremely common - even if tagged unions use overlapping syntax that doesn't group nicely, every "unit enum" will have cases with no blocks. Still, it's not a big deal either way, and as you say can easily be added later. Regards,
-- Rowan Tommins [IMSoP]

A.L.E.C

5 years ago
On 28.12.2020 21:21, Larry Garfield wrote:
> https://wiki.php.net/rfc/enumerations
Why can't this be simplified to: enum Size { case Small; case Medium; case Large; } 'Small' === Size::Small->value; // true Size::from('Small') === Size::Small; // true enum Suit { case Hearts = 'H'; case Diamonds = 'D'; case Clubs = 'C'; case Spades = 'S'; } 'H' === Suit::Hearts->value; // true 'Hearts' === Suit::Hearts->value; // false Also, how about using a method instead of property for ->value?
-- Aleksander Machniak Kolab Groupware Developer [https://kolab.org] Roundcube Webmail Developer [https://roundcube.net] ---------------------------------------------------- PGP: 19359DC1 # Blog: https://kolabian.wordpress.com

Larry Garfield

5 years ago
On Wed, Dec 30, 2020, at 12:30 PM, Aleksander Machniak wrote:
> On 28.12.2020 21:21, Larry Garfield wrote: > > https://wiki.php.net/rfc/enumerations > Why can't this be simplified to: > > enum Size { > case Small; > case Medium; > case Large; > } > > 'Small' === Size::Small->value; // true > Size::from('Small') === Size::Small; // true > > enum Suit { > case Hearts = 'H'; > case Diamonds = 'D'; > case Clubs = 'C'; > case Spades = 'S'; > } > > 'H' === Suit::Hearts->value; // true > 'Hearts' === Suit::Hearts->value; // false
That's a possibility we've been kicking around. I'm considering it, Ilija doesn't care for it. :-) What's your argument in favor? Related: There is an internal "case" value that is used as well. It could be exposed as a normal read-only property, which would always be equal to the name of the case ("Hearts"), regardless of whether another "value" is defined. We're still undecided on that, too.
> Also, how about using a method instead of property for ->value?
We originally did. Ilija preferred moving it to a property since there will need to be one anyway to store the value. I moderately favor a method but don't greatly care either way. --Larry Garfield

A.L.E.C

5 years ago
On 30.12.2020 21:21, Larry Garfield wrote:
>> enum Suit { >> case Hearts = 'H'; >> case Diamonds = 'D'; >> case Clubs = 'C'; >> case Spades = 'S'; >> } >> >> 'H' === Suit::Hearts->value; // true >> 'Hearts' === Suit::Hearts->value; // false > > That's a possibility we've been kicking around. I'm considering it, Ilija doesn't care for it. :-) What's your argument in favor?
My argument is that, from an end-user perspective, I don't really see why Unit and Scalar enums have to have different "API" at this point. I'm talking about ":string"/":int" in the enum definiton as well as ->value and ->from().
-- Aleksander Machniak Kolab Groupware Developer [https://kolab.org] Roundcube Webmail Developer [https://roundcube.net] ---------------------------------------------------- PGP: 19359DC1 # Blog: https://kolabian.wordpress.com

Rowan Collins

5 years ago
On 30/12/2020 21:24, Aleksander Machniak wrote:
> My argument is that, from an end-user perspective, I don't really see > why Unit and Scalar enums have to have different "API" at this point. > I'm talking about ":string"/":int" in the enum definiton as well as > ->value and ->from().
My personal opinion is that for many enums, explicitly not having a scalar representation is a good thing. This is basically similar to my opinion of __toString() etc: if you have *multiple* ways to convert something to/from a scalar, blessing one of them as "default" is arbitrary and confusing. For example: enum BookingStatus {      case PENDING;      case CONFIRMED;      case CANCELLED;      public function getId() {            return match($this) {                 self::PENDING => 1,                 self::CONFIRMED => 2,                 self::CANCELLED => 3,            };      }      public function getCode() {            return match($this) {                 self::PENDING => 'PEN',                 self::CONFIRMED => 'CON',                 self::CANCELLED => 'CAN',            };      }      public function getEnglishDescription() {            return match($this) {                 self::PENDING => 'Pending Payment',                 self::CONFIRMED => 'Confirmed',                 self::CANCELLED => 'Cancelled',            };      } } Regards,
-- Rowan Tommins [IMSoP]

Larry Garfield

5 years ago
On Thu, Dec 31, 2020, at 6:53 AM, Rowan Tommins wrote:
> On 30/12/2020 21:24, Aleksander Machniak wrote: > > My argument is that, from an end-user perspective, I don't really see > > why Unit and Scalar enums have to have different "API" at this point. > > I'm talking about ":string"/":int" in the enum definiton as well as > > ->value and ->from(). > > > My personal opinion is that for many enums, explicitly not having a > scalar representation is a good thing. > > This is basically similar to my opinion of __toString() etc: if you have > *multiple* ways to convert something to/from a scalar, blessing one of > them as "default" is arbitrary and confusing. > > For example: > > enum BookingStatus { >      case PENDING; >      case CONFIRMED; >      case CANCELLED; > >      public function getId() { >            return match($this) { >                 self::PENDING => 1, >                 self::CONFIRMED => 2, >                 self::CANCELLED => 3, >            }; >      } >      public function getCode() { >            return match($this) { >                 self::PENDING => 'PEN', >                 self::CONFIRMED => 'CON', >                 self::CANCELLED => 'CAN', >            }; >      } >      public function getEnglishDescription() { >            return match($this) { >                 self::PENDING => 'Pending Payment', >                 self::CONFIRMED => 'Confirmed', >                 self::CANCELLED => 'Cancelled', >            }; >      } > }
That is similar to our reasoning. It creates a foot-gun situation where someone could get in the habit of assuming that an enum always has a *reasonable* and *logical* and thus *reliable* string equivalent, when not all enums will have string equivalents that it's reasonable and logical to use. So, one less foot gun. Also, one of the extensions planned, as noted, is ADTs/tagged unions. Those could not have a primitive equivalent, since they're not singletons. Keeping UnitEnum and ScalarEnum separate allows us to later add TaggedEnum (or similar) that also extends UnitEnum, but not ScalarEnum. --Larry Garfield

Mike Schinkel

5 years ago
> On Dec 31, 2020, at 12:15 PM, Larry Garfield <larry@garfieldtech.com> wrote: > > On Thu, Dec 31, 2020, at 6:53 AM, Rowan Tommins wrote: >> On 30/12/2020 21:24, Aleksander Machniak wrote: >>> My argument is that, from an end-user perspective, I don't really see >>> why Unit and Scalar enums have to have different "API" at this point. >>> I'm talking about ":string"/":int" in the enum definiton as well as >>> ->value and ->from(). >> >> >> My personal opinion is that for many enums, explicitly not having a >> scalar representation is a good thing. >> >> This is basically similar to my opinion of __toString() etc: if you have >> *multiple* ways to convert something to/from a scalar, blessing one of >> them as "default" is arbitrary and confusing. >> >> For example: >> >> enum BookingStatus { >> case PENDING; >> case CONFIRMED; >> case CANCELLED; >> >> public function getId() { >> return match($this) { >> self::PENDING => 1, >> self::CONFIRMED => 2, >> self::CANCELLED => 3, >> }; >> } >> public function getCode() { >> return match($this) { >> self::PENDING => 'PEN', >> self::CONFIRMED => 'CON', >> self::CANCELLED => 'CAN', >> }; >> } >> public function getEnglishDescription() { >> return match($this) { >> self::PENDING => 'Pending Payment', >> self::CONFIRMED => 'Confirmed', >> self::CANCELLED => 'Cancelled', >> }; >> } >> } > > That is similar to our reasoning. It creates a foot-gun situation where someone could get in the habit of assuming that an enum always has a *reasonable* and *logical* and thus *reliable* string equivalent, when not all enums will have string equivalents that it's reasonable and logical to use. So, one less foot gun.
However, avoiding one foot-gun does not always mean that you avoid all foot-guns. For example, when you need to create a large number of string enums where the name and the symbol are the same, it would be very easy to have a typo in one but not the other, especially as a result of copy and paste editing. So in my perfect world this: enum BookingStatus { case PENDING; case CONFIRMED; case CANCELLED; } Would be equivalent to: enum BookingStatus { case PENDING = "PENDING"; case CONFIRMED = "CONFIRMED"; case CANCELLED = "CANCELLED"; } #fwiw

Markus Fischer

5 years ago
Hi, On 03.01.21 12:01, Mike Schinkel wrote:
> So in my perfect world this: > > enum BookingStatus { > case PENDING; > case CONFIRMED; > case CANCELLED; > } > > Would be equivalent to: > > enum BookingStatus { > case PENDING = "PENDING"; > case CONFIRMED = "CONFIRMED"; > case CANCELLED = "CANCELLED"; > }
I'm with Mikes' suggesting here, see also my previous messages [1] [2]. I don't know how to back this up with numbers, but the way I see it the majority of use cases will have a benefit of being able to directly use the literal values derived from the lexical ones and the ability to have custom values is feature next to it. [1] https://externals.io/message/112626#112655 [2] https://externals.io/message/112626#112663

Rowan Collins

5 years ago
On 04/01/2021 11:15, Markus Fischer wrote:
> On 03.01.21 12:01, Mike Schinkel wrote: >> So in my perfect world this: >> >> enum BookingStatus { >>       case PENDING; >>       case CONFIRMED; >>       case CANCELLED; >> } >> >> Would be equivalent to: >> >> enum BookingStatus { >>       case PENDING = "PENDING"; >>       case CONFIRMED = "CONFIRMED"; >>       case CANCELLED = "CANCELLED"; >> } > > I'm with Mikes' suggesting here, see also my previous messages [1] [2]. > > I don't know how to back this up with numbers, but the way I see it > the majority of use cases will have a benefit of being able to > directly use the literal values derived from the lexical ones and the > ability to have custom values is feature next to it.
I would personally be OK with this if it was allowed but opt-in, e.g. adding the ": string" would default the values in that way, or even something magic like ": auto". I'm still not a fan of having values *always* defined, because I want to be able to pick case labels without worrying about whether they make sense as values. For instance, there's a similar maintenance issue to named parameters: having a default value means if I write "case PENDING;" in v1.0, I can't change that to "case PENDING='P';" or "case PENDING=1;" in v1.1, in case somebody is relying on the value being 'PENDING', which was never intended. Regards,
-- Rowan Tommins [IMSoP]

Markus Fischer

5 years ago
Hi, On 04.01.21 15:05, Rowan Tommins wrote:
> On 04/01/2021 11:15, Markus Fischer wrote: >> On 03.01.21 12:01, Mike Schinkel wrote: >>> So in my perfect world this: >>> >>> enum BookingStatus { >>>       case PENDING; >>>       case CONFIRMED; >>>       case CANCELLED; >>> } >>> >>> Would be equivalent to: >>> >>> enum BookingStatus { >>>       case PENDING = "PENDING"; >>>       case CONFIRMED = "CONFIRMED"; >>>       case CANCELLED = "CANCELLED"; >>> } >> >> I'm with Mikes' suggesting here, see also my previous messages [1] [2]. >> >> I don't know how to back this up with numbers, but the way I see it >> the majority of use cases will have a benefit of being able to >> directly use the literal values derived from the lexical ones and the >> ability to have custom values is feature next to it. > > > I would personally be OK with this if it was allowed but opt-in, e.g. > adding the ": string" would default the values in that way, or even > something magic like ": auto".
I can't say whether just `: string` is too much, but in general I like it. I can follow the reasoning having no value by default and opt this in. The opt-in you suggested is very low-overhead (albeit a bit subtle, but maybe someone has a smarter idea :) Maybe not evident, I like the enum RFC but think the most value for many use cases will having automatically values. thanks! - Markus

Mike Schinkel

5 years ago
> On Jan 4, 2021, at 9:05 AM, Rowan Tommins <rowan.collins@gmail.com> wrote: > > On 04/01/2021 11:15, Markus Fischer wrote: >> On 03.01.21 12:01, Mike Schinkel wrote: >>> So in my perfect world this: >>> >>> enum BookingStatus { >>> case PENDING; >>> case CONFIRMED; >>> case CANCELLED; >>> } >>> >>> Would be equivalent to: >>> >>> enum BookingStatus { >>> case PENDING = "PENDING"; >>> case CONFIRMED = "CONFIRMED"; >>> case CANCELLED = "CANCELLED"; >>> } >> >> I'm with Mikes' suggesting here, see also my previous messages [1] [2]. >> >> I don't know how to back this up with numbers, but the way I see it the majority of use cases will have a benefit of being able to directly use the literal values derived from the lexical ones and the ability to have custom values is feature next to it. > > > I would personally be OK with this if it was allowed but opt-in, e.g. adding the ": string" would default the values in that way, or even something magic like ": auto". > > I'm still not a fan of having values *always* defined, because I want to be able to pick case labels without worrying about whether they make sense as values.
> > For instance, there's a similar maintenance issue to named parameters: having a default value means if I write "case PENDING;" in v1.0, I can't change that to "case PENDING='P';" or "case PENDING=1;" in v1.1, in case somebody is relying on the value being 'PENDING', which was never intended.
You make a good point about being able to define without defaults, and for being opt-in instead. A use-case I am thinking about is a long list of application-specific error codes where you don't want to have to come up two values for each error code and/or worry about keeping them the same. I don't quit get how you are thinking of with ": string"; can you give an example? Conversely, (something like) this might be a viable way to opt-in default values: enum defaults BookingStatus { case PENDING; case CONFIRMED; case CANCELLED; } -Mike

Michał Brzuchalski

5 years ago
Hi Larry, pon., 28 gru 2020 o 21:22 Larry Garfield <larry@garfieldtech.com> napisał(a):
> Hello, Internalians! > > After considerable discussion and effort, Ilija and I are ready to offer > you round 2 on enumerations. This is in the spirit of the previous > discussion, but based on that discussion a great deal has been reworked. > The main change is that Enumeration Cases are now object instances of the > Enumeration class rather than their own class. Most of the other changes > are knock-on effects of that. > > Of particular note: > > * Cases may not have methods or constants on them. They're just dumb > values. > * Enums themselves may have methods, static methods, or constants. > * Traits are supported, as long as they don't have properties. > * The value() method on scalar enums is now a property. > > The full RFC is here, and I recommend reading it again in full given how > much was updated. > > https://wiki.php.net/rfc/enumerations > > The implementation is 98% complete; there's still a few lagging bits in > reflection, and some opcache bugs that Ilija is still stomping on. >
I really like the shape of the current RFC. I'd like to ask a few things: 1. Regarding the Scalar Enums since scalar values need to be literal and by design they're read only why they use a spear "->value" on enumeration? A spear "->" in objects is dedicated to class properties and by default they make thinking of property which in most cases is read-write visible. An example using Suit enumeration from RFC: // when I assign an enumeration to variable $var = Suit::Spades; // then reaching it's value using "->value" echo $var->value; // 'C' // Looks just like an object property fetch By default if a property is visible it is write accessible as well, which may confuse. Instead of using spear "->value" would it be possible to fetch the value just like object constants? print Suit::Clubs::value; // 'C' echo $var::value; // 'C' This mentally indicates by default that it's value is constant, simple scalar value and read only! 2. Regarding the Scalar Enums declaration syntax enum Suit: string { case Hearts = 'H'; } Would it make sense to move the part declaring enumeration value before enum name? I think it was put here to look similar to function return type, but IMHO it looks better and reads easier when moved before enum name: enum:string Suit { case Hearts = 'H'; } Leaving the space between enum name and bracket for further extensions in future. What do you think? Cheers, Michał Marcin Brzuchalski

Larry Garfield

5 years ago
On Thu, Dec 31, 2020, at 4:02 AM, Michał Marcin Brzuchalski wrote:
> Hi Larry,
> I really like the shape of the current RFC. > > I'd like to ask a few things: > > 1. Regarding the Scalar Enums since scalar values need to be literal and > by design they're read only why they use a spear "->value" on enumeration? > A spear "->" in objects is dedicated to class properties and by default > they make thinking of property which > in most cases is read-write visible. > > An example using Suit enumeration from RFC: > // when I assign an enumeration to variable > $var = Suit::Spades; > // then reaching it's value using "->value" > echo $var->value; // 'C' > // Looks just like an object property fetch > > By default if a property is visible it is write accessible as well, which > may confuse. > > Instead of using spear "->value" would it be possible to fetch the value > just like object constants? > > print Suit::Clubs::value; // 'C' > echo $var::value; // 'C' > > This mentally indicates by default that it's value is constant, simple > scalar value and read only!
We originally used a method in the first draft. We changed it to a property for three reasons. 1) There's lots of talk lately about immutable properties, so one way or another "it's a property so I can write to it" is an assumption that won't hold much longer. 2) The property has to exist internally anyway, because under the hood it's just a class with a property, so the name would still be reserved either way. 3) Given 1 and 2, adding a method on top just adds some micro overhead for the method call. A constant-esque syntax wouldn't be possible because under the hood, the cases are objects now, not classes. We could put constants on Suit, but Suit::Clubs is an object, and objects can't have constants independent of their class.
> 2. Regarding the Scalar Enums declaration syntax > > enum Suit: string { > case Hearts = 'H'; > } > > Would it make sense to move the part declaring enumeration value before > enum name? > I think it was put here to look similar to function return type, but IMHO > it looks better and reads easier > when moved before enum name: > > enum:string Suit { > case Hearts = 'H'; > } > > Leaving the space between enum name and bracket for further extensions in > future. > What do you think?
That would be a very one-off syntax. The colon-suffix is already established in PHP, and i the same as the syntax used in Swift. What you're describing looks a lot more like a generic. Which... I suppose in some ways this is if you squint really hard? I don't have much of an argument against it other than aesthetic. I have no idea what the parser would do with it. --Larry Garfield

Benjamin Eberlei

5 years ago
On Mon, Dec 28, 2020 at 9:22 PM Larry Garfield <larry@garfieldtech.com> wrote:
> Hello, Internalians! > > After considerable discussion and effort, Ilija and I are ready to offer > you round 2 on enumerations. This is in the spirit of the previous > discussion, but based on that discussion a great deal has been reworked. > The main change is that Enumeration Cases are now object instances of the > Enumeration class rather than their own class. Most of the other changes > are knock-on effects of that. > > Of particular note: > > * Cases may not have methods or constants on them. They're just dumb > values. > * Enums themselves may have methods, static methods, or constants. > * Traits are supported, as long as they don't have properties. > * The value() method on scalar enums is now a property. > > The full RFC is here, and I recommend reading it again in full given how > much was updated. > > https://wiki.php.net/rfc/enumerations > > The implementation is 98% complete; there's still a few lagging bits in > reflection, and some opcache bugs that Ilija is still stomping on. > > There are a few outstanding questions listed that we would like feedback > on. We're not entirely certain which direction to go with them, for > reasons explained in the RFC. Input on those is especially welcome. > > Happy New Year. May it be enumerable. >
I think the reflection part is the weakest of this proposal, in my opinion there should not be a ReflectionEnum and ReflectionCase. - ReflectionEnum extends ReflectionClass is problematic - is hasEnum just an alias for hasConstant? - Same for getCases() for getConstants(). - what does getConstant() or ReflectionClassConstant::getValue return for a non scalar enum? the instance? In my opinion you should embrace the "desugarizing" like constructor promotion and just keep everything with the exsiting Reflection structure.

Larry Garfield

5 years ago
On Fri, Jan 1, 2021, at 12:08 PM, Benjamin Eberlei wrote:
> On Mon, Dec 28, 2020 at 9:22 PM Larry Garfield <larry@garfieldtech.com> > wrote: > > > Hello, Internalians! > > > > After considerable discussion and effort, Ilija and I are ready to offer > > you round 2 on enumerations. This is in the spirit of the previous > > discussion, but based on that discussion a great deal has been reworked. > > The main change is that Enumeration Cases are now object instances of the > > Enumeration class rather than their own class. Most of the other changes > > are knock-on effects of that. > > > > Of particular note: > > > > * Cases may not have methods or constants on them. They're just dumb > > values. > > * Enums themselves may have methods, static methods, or constants. > > * Traits are supported, as long as they don't have properties. > > * The value() method on scalar enums is now a property. > > > > The full RFC is here, and I recommend reading it again in full given how > > much was updated. > > > > https://wiki.php.net/rfc/enumerations > > > > The implementation is 98% complete; there's still a few lagging bits in > > reflection, and some opcache bugs that Ilija is still stomping on. > > > > There are a few outstanding questions listed that we would like feedback > > on. We're not entirely certain which direction to go with them, for > > reasons explained in the RFC. Input on those is especially welcome. > > > > Happy New Year. May it be enumerable. > > > > I think the reflection part is the weakest of this proposal, in my opinion > there should not be a ReflectionEnum and ReflectionCase.
I'd agree that Reflection is the place we still have the most open questions. (The listed attribute open questions are synonyms for reflection questions, in a sense.) I disagree with removing their reflection classes entirely, though.
> - ReflectionEnum extends ReflectionClass is problematic
Why? The main alternative is making it its own thing entirely, which seemed redundant.
> - is hasEnum just an alias for hasConstant?
Assuming you mean hasCase(), no. An enum can have constants now in addition to cases. So for instance: enum Foo { case Bar; const Beep = self::Bar; } hasCase('Beep') would return false. hasConstant('Beep') returns true.
> - Same for getCases() for getConstants().
As above. Not all constants are cases. That cases are constants is an implementation convenience.
> - what does getConstant() or ReflectionClassConstant::getValue return for a > non scalar enum? the instance?
ReflectionEnum::getConstant() would return the case instance object, whether or not it's scalar.
> In my opinion you should embrace the "desugarizing" like constructor > promotion and just keep everything with the exsiting Reflection structure.
Enums are more than "just" sugar on objects, though. They're built on objects, but they have limitations that objects don't have, and features objects don't have. And ideally those will expand in future RFCs. Having no difference at all between them an objects in reflection even though their functionality is different at a user level seems needlessly confusing and limiting to me. --Larry Garfield

Nikita Popov

5 years ago
On Mon, Dec 28, 2020 at 9:22 PM Larry Garfield <larry@garfieldtech.com> wrote:
> Hello, Internalians! > > After considerable discussion and effort, Ilija and I are ready to offer > you round 2 on enumerations. This is in the spirit of the previous > discussion, but based on that discussion a great deal has been reworked. > The main change is that Enumeration Cases are now object instances of the > Enumeration class rather than their own class. Most of the other changes > are knock-on effects of that. > > Of particular note: > > * Cases may not have methods or constants on them. They're just dumb > values. > * Enums themselves may have methods, static methods, or constants. > * Traits are supported, as long as they don't have properties. > * The value() method on scalar enums is now a property. > > The full RFC is here, and I recommend reading it again in full given how > much was updated. > > https://wiki.php.net/rfc/enumerations > > The implementation is 98% complete; there's still a few lagging bits in > reflection, and some opcache bugs that Ilija is still stomping on. > > There are a few outstanding questions listed that we would like feedback > on. We're not entirely certain which direction to go with them, for > reasons explained in the RFC. Input on those is especially welcome. > > Happy New Year. May it be enumerable.
Nice work, I like the updated proposal. Some notes:
> Similarly, enum names and case names are both case insensitive.
I agree that enum names should be case insensitive (like class names), but why should case names be case insensitive? The closest analogon to a case would be a class constant, and those are case sensitive.
> All Cases have a read-only property, case, that is the case-sensitive
name of the case itself. I can see how this makes sense, but it wouldn't be my first guess as to how you access the case name. I'm wondering if using $enumValue->name or $enumValue->caseName might be preferable.
> Scalar equivalent values must be literals. Constants and constant
expressions are not supported. Why? This seems inconsistent with the overall language. If I can use a constant expression as a class constant value, why can't I use it as an enum value?
> ScalarEnum exposes an additional static method from() that is
automatically generated. I think it would be good to be slightly more explicit here and call it fromValue(). (We could have fromName() to construct it from a case name, and any number of custom from* named constructors.)
> The from() method will up-cast from a scalar to its corresponding
Enumerated Case. Invalid scalars with no matching Case will throw a ValueError. There is also a has() method, which will return boolean true if a case with that value exists and false otherwise Just a thought, but rather than having has() and from(), it may make sense to have from() (throws if invalid) and tryFrom() (returns null if invalid). I think a has() method is pretty much useless in isolation, it will always be used in a combination of has() and from(), in which case it is better to combine them rather than have an implicit contract between them.
> Manually defining a static from() or has() method on a Scalar Enum will
result in a fatal error. Or a non-static one :) You can't define a static and a non-static method with the same name in PHP.
> Enums and cases may have attributes attached to them, like any other
language construct. The TARGET_CLASS target filter will include Enums themselves. The TARGET_CONST target filter will include Enum Cases TARGET_CONST should presumably be TARGET_CLASS_CONST. More generally though, I wonder if there should be a separate TARGET_CASE...
> Returns the scalar equivalent type of the Enum, if any. If it doesn't
have one, it returns a ReflectionType on null. Possibly I'm misunderstanding the sentence, but why does it return a "ReflectionType on null" rather than just "null"? It's not like ::$value will have a null value in this case, it will not exist at all.
> Additionally, a new function is_enum(mixed): bool returns true if the
value passed is an enum or case object. Could you please clarify what "an enum or case object" means? Is this function both for checking whether an object is a case object and a (string) class an enum class? Some typo/etc notes on the text:
> All Unit Enums as implemented as instances of their enum type.
"as" -> "are" possibly?
> That is one way to determine an Enum from any other object:
"determine" -> "distinguish" possibly?
> The above hierarchy is logically similar to the following class structure
(although this is not the actual code that runs): You might want to change the constructor to private function __construct(public string $case) {} in this example. The use of "new static" for a final class is also confusing. Regards, Nikita

Larry Garfield

5 years ago
On Tue, Jan 5, 2021, at 8:26 AM, Nikita Popov wrote:
> Nice work, I like the updated proposal. Some notes: > > > Similarly, enum names and case names are both case insensitive. > > I agree that enum names should be case insensitive (like class names), but > why should case names be case insensitive? The closest analogon to a case > would be a class constant, and those are case sensitive.
This is a left over from the earlier draft when Cases were also classes. I've corrected it. Thanks for the catch.
> > All Cases have a read-only property, case, that is the case-sensitive > name of the case itself. > > I can see how this makes sense, but it wouldn't be my first guess as to how > you access the case name. I'm wondering if using $enumValue->name or > $enumValue->caseName might be preferable.
We'll change it to ->name.
> > Scalar equivalent values must be literals. Constants and constant > expressions are not supported. > > Why? This seems inconsistent with the overall language. If I can use a > constant expression as a class constant value, why can't I use it as an > enum value?
Mostly because it was more challenging to do. We're fine with including it if Ilija can make it work. (Suggestions for him on how to do so most efficiently are welcome.)
> > ScalarEnum exposes an additional static method from() that is > automatically generated. > > I think it would be good to be slightly more explicit here and call it > fromValue(). (We could have fromName() to construct it from a case name, > and any number of custom from* named constructors.) > > > The from() method will up-cast from a scalar to its corresponding > Enumerated Case. Invalid scalars with no matching Case will throw a > ValueError. There is also a has() method, which will return boolean true if > a case with that value exists and false otherwise > > Just a thought, but rather than having has() and from(), it may make sense > to have from() (throws if invalid) and tryFrom() (returns null if invalid). > I think a has() method is pretty much useless in isolation, it will always > be used in a combination of has() and from(), in which case it is better to > combine them rather than have an implicit contract between them.
Hm, maybe. My concern is that "try" to me implies "exceptions are involved somewhere", since PHP doesn't have the "TryX means nullable return" convention that Rust or C# have. Anyone else want to weigh in?
> > Manually defining a static from() or has() method on a Scalar Enum will > result in a fatal error. > > Or a non-static one :) You can't define a static and a non-static method > with the same name in PHP.
Fixed.
> > Enums and cases may have attributes attached to them, like any other > language construct. The TARGET_CLASS target filter will include Enums > themselves. The TARGET_CONST target filter will include Enum Cases > > TARGET_CONST should presumably be TARGET_CLASS_CONST. More generally > though, I wonder if there should be a separate TARGET_CASE...
We had that in the spec until like 40 hours ago. :-) We removed it mainly because beberlei was pushing back on it. (And the corresponding reflection classes, which are still under discussion.) Personally I'd rather have the flexibility of targeting enums and cases specifically. Ilija and Benjamin feel it's better to "embrace the object-y-ness" of enums and just use the existing targets. It's not a hill I'm going to die on either way, so if you can convince them I'm happy to add them back in.
> > Returns the scalar equivalent type of the Enum, if any. If it doesn't > have one, it returns a ReflectionType on null. > > Possibly I'm misunderstanding the sentence, but why does it return a > "ReflectionType on null" rather than just "null"? It's not like ::$value > will have a null value in this case, it will not exist at all.
More likely I'm misunderstanding the nuances of the reflection API. The ideal here is to avoid a nullable return if at all possible. As noted above reflection is the shakiest part left right now, so if you have specific suggestions for how it would work better, please share.
> > Additionally, a new function is_enum(mixed): bool returns true if the > value passed is an enum or case object. > > Could you please clarify what "an enum or case object" means? Is this > function both for checking whether an object is a case object and a > (string) class an enum class?
It would return true if given an enum case, a variable that is assigned to an enum case, or a class name string that refers to a class that is an enum. Other than replicating the test for it I;m not sure how better to describe it. (Or, just use that sentence?)
> Some typo/etc notes on the text: > > > All Unit Enums as implemented as instances of their enum type. > > "as" -> "are" possibly? > > > That is one way to determine an Enum from any other object: > > "determine" -> "distinguish" possibly? > > > The above hierarchy is logically similar to the following class structure > (although this is not the actual code that runs): > > You might want to change the constructor to > > private function __construct(public string $case) {} > > in this example. The use of "new static" for a final class is also > confusing.
Got them all, thanks. (new static is because at this point I basically never use self and always use static, as it's what I actually want 99.4% of the time.) --Larry Garfield

Nikita Popov

5 years ago
On Tue, Jan 5, 2021 at 6:56 PM Larry Garfield <larry@garfieldtech.com> wrote:
> On Tue, Jan 5, 2021, at 8:26 AM, Nikita Popov wrote: > > > Nice work, I like the updated proposal. Some notes: > > > > > Similarly, enum names and case names are both case insensitive. > > > > I agree that enum names should be case insensitive (like class names), > but > > why should case names be case insensitive? The closest analogon to a case > > would be a class constant, and those are case sensitive. > > This is a left over from the earlier draft when Cases were also classes. > I've corrected it. Thanks for the catch. > > > > All Cases have a read-only property, case, that is the case-sensitive > > name of the case itself. > > > > I can see how this makes sense, but it wouldn't be my first guess as to > how > > you access the case name. I'm wondering if using $enumValue->name or > > $enumValue->caseName might be preferable. > > We'll change it to ->name. > > > > Scalar equivalent values must be literals. Constants and constant > > expressions are not supported. > > > > Why? This seems inconsistent with the overall language. If I can use a > > constant expression as a class constant value, why can't I use it as an > > enum value? > > Mostly because it was more challenging to do. We're fine with including > it if Ilija can make it work. (Suggestions for him on how to do so most > efficiently are welcome.) >
It may be okay to retain the limitation, but in that case the RFC should include some technical justification for that. It would also be good to explicitly state whether something like "1 + 1" is allowed, i.e. whether constant expressions are forbidden entirely, or only those that cannot be evaluated at compile-time.
> > > ScalarEnum exposes an additional static method from() that is > > automatically generated. > > > > I think it would be good to be slightly more explicit here and call it > > fromValue(). (We could have fromName() to construct it from a case name, > > and any number of custom from* named constructors.) > > > > > The from() method will up-cast from a scalar to its corresponding > > Enumerated Case. Invalid scalars with no matching Case will throw a > > ValueError. There is also a has() method, which will return boolean true > if > > a case with that value exists and false otherwise > > > > Just a thought, but rather than having has() and from(), it may make > sense > > to have from() (throws if invalid) and tryFrom() (returns null if > invalid). > > I think a has() method is pretty much useless in isolation, it will > always > > be used in a combination of has() and from(), in which case it is better > to > > combine them rather than have an implicit contract between them. > > Hm, maybe. My concern is that "try" to me implies "exceptions are > involved somewhere", since PHP doesn't have the "TryX means nullable > return" convention that Rust or C# have. > > Anyone else want to weigh in? > > > > Manually defining a static from() or has() method on a Scalar Enum will > > result in a fatal error. > > > > Or a non-static one :) You can't define a static and a non-static method > > with the same name in PHP. > > Fixed. > > > > Enums and cases may have attributes attached to them, like any other > > language construct. The TARGET_CLASS target filter will include Enums > > themselves. The TARGET_CONST target filter will include Enum Cases > > > > TARGET_CONST should presumably be TARGET_CLASS_CONST. More generally > > though, I wonder if there should be a separate TARGET_CASE... > > We had that in the spec until like 40 hours ago. :-) We removed it mainly > because beberlei was pushing back on it. (And the corresponding reflection > classes, which are still under discussion.) > > Personally I'd rather have the flexibility of targeting enums and cases > specifically. Ilija and Benjamin feel it's better to "embrace the > object-y-ness" of enums and just use the existing targets. It's not a hill > I'm going to die on either way, so if you can convince them I'm happy to > add them back in. >
Can't say I have a particularly strong argument either way here :)
> > > Returns the scalar equivalent type of the Enum, if any. If it doesn't > > have one, it returns a ReflectionType on null. > > > > Possibly I'm misunderstanding the sentence, but why does it return a > > "ReflectionType on null" rather than just "null"? It's not like ::$value > > will have a null value in this case, it will not exist at all. > > More likely I'm misunderstanding the nuances of the reflection API. The > ideal here is to avoid a nullable return if at all possible. As noted > above reflection is the shakiest part left right now, so if you have > specific suggestions for how it would work better, please share. >
Well, why do you want to avoid a nullable return here? We have many other getType/getReturnType style APIs that all return null if no type is specified, so I would expect this one to work the same way. Returning ReflectionType("null") is disingenuous, as the enum was neither declared as "enum Foo: null {}", nor does it behave as if it were declared as such.
> > > Additionally, a new function is_enum(mixed): bool returns true if the > > value passed is an enum or case object. > > > > Could you please clarify what "an enum or case object" means? Is this > > function both for checking whether an object is a case object and a > > (string) class an enum class? > > It would return true if given an enum case, a variable that is assigned to > an enum case, or a class name string that refers to a class that is an > enum. Other than replicating the test for it I;m not sure how better to > describe it. (Or, just use that sentence?) >
Including an example would be good. I just checked the implementation, and apparently the actual signature is "is_enum(mixed $value, bool $autoload = true): bool", which brings me to the concern I have about this API: Mixing checking for classes, which requires autoloading, and for other values is generally a bad idea -- been there, done that. The problem is that if used to check whether a value is an enum instance, then just naively writing is_enum($value) may trigger autoloading when $value happens to be a string. This is not just inefficient, but also a possible security issue. The $autoload flag can mitigate that, but that's just a workaround for bad function design. This could be addressed in different ways, e.g. one could have separate is_enum_class() and is_enum_value() functions, though the first one would probably more typically be called enum_exists(). I'm not sure if that is the right way to address it though, as the intended use case for the is_enum() function is not entirely clear to me. Regards, Nikita

Larry Garfield

5 years ago
On Mon, Dec 28, 2020, at 2:21 PM, Larry Garfield wrote:
> Hello, Internalians! > > After considerable discussion and effort, Ilija and I are ready to > offer you round 2 on enumerations. This is in the spirit of the > previous discussion, but based on that discussion a great deal has been > reworked. The main change is that Enumeration Cases are now object > instances of the Enumeration class rather than their own class. Most > of the other changes are knock-on effects of that. > > Of particular note: > > * Cases may not have methods or constants on them. They're just dumb values. > * Enums themselves may have methods, static methods, or constants. > * Traits are supported, as long as they don't have properties. > * The value() method on scalar enums is now a property. > > The full RFC is here, and I recommend reading it again in full given > how much was updated. > > https://wiki.php.net/rfc/enumerations > > The implementation is 98% complete; there's still a few lagging bits in > reflection, and some opcache bugs that Ilija is still stomping on. > > There are a few outstanding questions listed that we would like > feedback on. We're not entirely certain which direction to go with > them, for reasons explained in the RFC. Input on those is especially > welcome. > > Happy New Year. May it be enumerable.
For those who would like to play the home game but haven't been able to, Tyson Andre has build a delightful Wasm version of the enums branch so you can run actual code using enums. https://tysonandre.github.io/php-rfc-demo/enums/ 100% credit to Tyson for putting that together. It's quite slick. :-) (It's a few commits behind but nothing that should impact most testing; just some edge formatting around print_r, reflection, and stuff like that.) Also, while we were at it, we went with the from()/tryFrom() method pair. No one seemed morally opposed to them and there wasn't really a better naming convention. Aside from some nitpicking around reflection and possibly fiddling with the naming of "Scalar Enum" et al, we're closing in on the final version. It will probably go to a vote in the not too distant future. --Larry Garfield

Girgias

5 years ago
On Mon, 25 Jan 2021 at 02:29, Larry Garfield <larry@garfieldtech.com> wrote:
> Aside from some nitpicking around reflection and possibly fiddling with > the naming of "Scalar Enum" et al, we're closing in on the final version. > It will probably go to a vote in the not too distant future. > > --Larry Garfield > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php >
Most of this has already been communicated off list but I ought to make my opinions on the proposal somewhat "official". Although I do think we need enums backed into PHP, I'm really not keen on piggy backing on objects. I'm also aware that this would be a larger undertaking as using objects as one would need to rewrite part of the logic objects use. However, looking at how much of the objectness needs to be blocked and/or modified (traits, final, no properties, etc.), IMHO, points towards a new zval structure instead of reusing objects, at least for the simple cases as a simple enumeration set or a scalar backed enumeration set which don't need any methods. Moreover, the implementation details of it leak quite a lot and I'm not really a fan of having the reflection class of enum extend the class one. My reasoning is that, albeit extremely clunky, one can already achieve the kind of enumeration proposed here in userland, in a type safe fashion, in PHP 8.0 by using union types: final class Heart {} final class Clubs {} final class Spades {} final class Diamond {} final class Suit { public function __construct(private Heart|Clubs|Spades|Diamond $value) {} public function value() { return $this->value; } } This form is basically reducing an ADT to an enumeration, and in any case introducing additional syntax to alleviate the verbosity of this is a separate concern. (to see this somewhat in action I've written some slides for small university talk which demonstrates how it can be used [1][2]). The final concern, which might be non-existent as there is no mention of it in the RFC, is OPcache. My understanding of OPcache is fairly limited, but IIRC caching and optimizing things around objects is extremely complicated if not at all impossible, wouldn't this rather severe limitation also apply to enums, even for basic lists? Or are all the objectness restrictions placed on enums sufficient to make this a non-issue? As such, unless convinced otherwise, I don't think I will personally support this proposal. Best regards, George P. Banyard [1] https://slides.com/girgias/reflection-algebraic-data-types-for-mortals#/5/5 [2] https://slides.com/girgias/reflection-algebraic-data-types-for-mortals#/5/6

Larry Garfield

5 years ago
On Mon, Dec 28, 2020, at 2:21 PM, Larry Garfield wrote:
> Hello, Internalians! > > After considerable discussion and effort, Ilija and I are ready to > offer you round 2 on enumerations. This is in the spirit of the > previous discussion, but based on that discussion a great deal has been > reworked. The main change is that Enumeration Cases are now object > instances of the Enumeration class rather than their own class. Most > of the other changes are knock-on effects of that. > > Of particular note: > > * Cases may not have methods or constants on them. They're just dumb values. > * Enums themselves may have methods, static methods, or constants. > * Traits are supported, as long as they don't have properties. > * The value() method on scalar enums is now a property. > > The full RFC is here, and I recommend reading it again in full given > how much was updated. > > https://wiki.php.net/rfc/enumerations > > The implementation is 98% complete; there's still a few lagging bits in > reflection, and some opcache bugs that Ilija is still stomping on. > > There are a few outstanding questions listed that we would like > feedback on. We're not entirely certain which direction to go with > them, for reasons explained in the RFC. Input on those is especially > welcome. > > Happy New Year. May it be enumerable.
And we're back again. The RFC has been updated with a steady stream of smaller improvements based on feedback and testing, and is now in its Final Form(tm) (we think). The only major change worth noting is that we renamed things. :-) An enum with no scalar backing is now called a Pure Enum, made up of Pure Cases. One that does have backing values is called a Backed Enum, made up of Backed Cases. That change is mainly to allow for future expansion to non-scalar backing static values, should the use case arise. Reflection was also reworked a bit to make it more logical. https://wiki.php.net/rfc/enumerations At this point, Ilija and I consider the RFC done and ready for a vote. Baring any major issues being brought up, we plan to start the vote in the first half of next week, probably Tuesday-ish. If you have any other bug reports or tweaks, please speak now or forever hold your patches. --Larry Garfield

Nikita Popov

5 years ago
On Fri, Jan 29, 2021 at 6:18 PM Larry Garfield <larry@garfieldtech.com> wrote:
> On Mon, Dec 28, 2020, at 2:21 PM, Larry Garfield wrote: > > Hello, Internalians! > > > > After considerable discussion and effort, Ilija and I are ready to > > offer you round 2 on enumerations. This is in the spirit of the > > previous discussion, but based on that discussion a great deal has been > > reworked. The main change is that Enumeration Cases are now object > > instances of the Enumeration class rather than their own class. Most > > of the other changes are knock-on effects of that. > > > > Of particular note: > > > > * Cases may not have methods or constants on them. They're just dumb > values. > > * Enums themselves may have methods, static methods, or constants. > > * Traits are supported, as long as they don't have properties. > > * The value() method on scalar enums is now a property. > > > > The full RFC is here, and I recommend reading it again in full given > > how much was updated. > > > > https://wiki.php.net/rfc/enumerations > > > > The implementation is 98% complete; there's still a few lagging bits in > > reflection, and some opcache bugs that Ilija is still stomping on. > > > > There are a few outstanding questions listed that we would like > > feedback on. We're not entirely certain which direction to go with > > them, for reasons explained in the RFC. Input on those is especially > > welcome. > > > > Happy New Year. May it be enumerable. > > And we're back again. The RFC has been updated with a steady stream of > smaller improvements based on feedback and testing, and is now in its Final > Form(tm) (we think). The only major change worth noting is that we renamed > things. :-) > > An enum with no scalar backing is now called a Pure Enum, made up of Pure > Cases. One that does have backing values is called a Backed Enum, made up > of Backed Cases. That change is mainly to allow for future expansion to > non-scalar backing static values, should the use case arise. Reflection > was also reworked a bit to make it more logical. > > https://wiki.php.net/rfc/enumerations > > At this point, Ilija and I consider the RFC done and ready for a vote. > Baring any major issues being brought up, we plan to start the vote in the > first half of next week, probably Tuesday-ish. If you have any other bug > reports or tweaks, please speak now or forever hold your patches. > > --Larry Garfield >
Two notes:
> “enum” becomes a language keyword, with the usual potential for naming
conflicts with existing global constants. ... and class names, which is probably more relevant here. It's worth noting that this RFC renders many existing enum libraries unusable, such as myclabs/php-enum, which defines an "abstract class Enum".
> Both Pure Enums and Backed Enums implement an internal interface named
IterableEnum I really don't like this interface name. We have an "iterable" type in PHP, and having an IterableEnum that is not ... iterable would be somewhat confusing. IIRC you previously called this UnitEnum, which is also not great in that "unitary enum" is not particularly well-established terminology, but I think it's still better than IterableEnum. Regards, Nikita

Nikita Popov

5 years ago
On Mon, Feb 1, 2021 at 2:16 PM Nikita Popov <nikita.ppv@gmail.com> wrote:
> On Fri, Jan 29, 2021 at 6:18 PM Larry Garfield <larry@garfieldtech.com> > wrote: > >> On Mon, Dec 28, 2020, at 2:21 PM, Larry Garfield wrote: >> > Hello, Internalians! >> > >> > After considerable discussion and effort, Ilija and I are ready to >> > offer you round 2 on enumerations. This is in the spirit of the >> > previous discussion, but based on that discussion a great deal has been >> > reworked. The main change is that Enumeration Cases are now object >> > instances of the Enumeration class rather than their own class. Most >> > of the other changes are knock-on effects of that. >> > >> > Of particular note: >> > >> > * Cases may not have methods or constants on them. They're just dumb >> values. >> > * Enums themselves may have methods, static methods, or constants. >> > * Traits are supported, as long as they don't have properties. >> > * The value() method on scalar enums is now a property. >> > >> > The full RFC is here, and I recommend reading it again in full given >> > how much was updated. >> > >> > https://wiki.php.net/rfc/enumerations >> > >> > The implementation is 98% complete; there's still a few lagging bits in >> > reflection, and some opcache bugs that Ilija is still stomping on. >> > >> > There are a few outstanding questions listed that we would like >> > feedback on. We're not entirely certain which direction to go with >> > them, for reasons explained in the RFC. Input on those is especially >> > welcome. >> > >> > Happy New Year. May it be enumerable. >> >> And we're back again. The RFC has been updated with a steady stream of >> smaller improvements based on feedback and testing, and is now in its Final >> Form(tm) (we think). The only major change worth noting is that we renamed >> things. :-) >> >> An enum with no scalar backing is now called a Pure Enum, made up of Pure >> Cases. One that does have backing values is called a Backed Enum, made up >> of Backed Cases. That change is mainly to allow for future expansion to >> non-scalar backing static values, should the use case arise. Reflection >> was also reworked a bit to make it more logical. >> >> https://wiki.php.net/rfc/enumerations >> >> At this point, Ilija and I consider the RFC done and ready for a vote. >> Baring any major issues being brought up, we plan to start the vote in the >> first half of next week, probably Tuesday-ish. If you have any other bug >> reports or tweaks, please speak now or forever hold your patches. >> >> --Larry Garfield >> > > Two notes: > > > “enum” becomes a language keyword, with the usual potential for naming > conflicts with existing global constants. > > ... and class names, which is probably more relevant here. It's worth > noting that this RFC renders many existing enum libraries unusable, such as > myclabs/php-enum, which defines an "abstract class Enum". > > > Both Pure Enums and Backed Enums implement an internal interface named > IterableEnum > > I really don't like this interface name. We have an "iterable" type in > PHP, and having an IterableEnum that is not ... iterable would be somewhat > confusing. IIRC you previously called this UnitEnum, which is also not > great in that "unitary enum" is not particularly well-established > terminology, but I think it's still better than IterableEnum. > > Regards, > Nikita >
I'm also not really happy with the ReflectionEnumPureCase vs ReflectionEnumBackedCase split in the reflection API. I think there should be some form of common base here. That could be ReflectionEnumBackedCase extends ReflectionEnumCase extends ReflectionClassConstant, or it could be ReflectionEnumUnitCase extends ReflectionClassConstant, where getBackingValue() can return null (previous design). What was the motivation for switching to two "unrelated" classes? One more thing I'm missing in the RFC and implementation both is an API for declaring enums from PHP extensions. As far as I can tell, this is currently not possible. Nikita

Bob Weinand

5 years ago
> Am 29.01.2021 um 18:15 schrieb Larry Garfield <larry@garfieldtech.com>: > > And we're back again. The RFC has been updated with a steady stream of smaller improvements based on feedback and testing, and is now in its Final Form(tm) (we think). The only major change worth noting is that we renamed things. :-) > > An enum with no scalar backing is now called a Pure Enum, made up of Pure Cases. One that does have backing values is called a Backed Enum, made up of Backed Cases. That change is mainly to allow for future expansion to non-scalar backing static values, should the use case arise. Reflection was also reworked a bit to make it more logical. > > https://wiki.php.net/rfc/enumerations <https://wiki.php.net/rfc/enumerations> > > At this point, Ilija and I consider the RFC done and ready for a vote. Baring any major issues being brought up, we plan to start the vote in the first half of next week, probably Tuesday-ish. If you have any other bug reports or tweaks, please speak now or forever hold your patches.
Hey, There is a single point in the RFC which is unreasonable to me, which is barring static properties. I do not consider static properties to be handled the same than normal properties. It's perfectly reasonable to prevent normal instance properties if we want [pure] enums to be stateless (which makes sense). It however does not make sense to arbitrarily restrict static properties. In the end a static property (on final classes at least) is mostly a nice way to express global state, which could be written "global $my_namespaced_enum_class_prop_name;" and used then. It's just that self::$prop_name is much more readable and user-friendly, including for types and static analysis. I also agree with Nikitas note that IterableEnum is really a suboptimal name. If you want to highlight finiteness, maybe call it FiniteEnum. UnitEnum would be fine too. Thanks, Bob

Alexandru Pătrănescu

5 years ago
On Fri, Jan 29, 2021 at 7:17 PM Larry Garfield <larry@garfieldtech.com> wrote:
> On Mon, Dec 28, 2020, at 2:21 PM, Larry Garfield wrote: > > Hello, Internalians! > > > > After considerable discussion and effort, Ilija and I are ready to > > offer you round 2 on enumerations. This is in the spirit of the > > previous discussion, but based on that discussion a great deal has been > > reworked. The main change is that Enumeration Cases are now object > > instances of the Enumeration class rather than their own class. Most > > of the other changes are knock-on effects of that. > > > > Of particular note: > > > > * Cases may not have methods or constants on them. They're just dumb > values. > > * Enums themselves may have methods, static methods, or constants. > > * Traits are supported, as long as they don't have properties. > > * The value() method on scalar enums is now a property. > > > > The full RFC is here, and I recommend reading it again in full given > > how much was updated. > > > > https://wiki.php.net/rfc/enumerations > > > > The implementation is 98% complete; there's still a few lagging bits in > > reflection, and some opcache bugs that Ilija is still stomping on. > > > > There are a few outstanding questions listed that we would like > > feedback on. We're not entirely certain which direction to go with > > them, for reasons explained in the RFC. Input on those is especially > > welcome. > > > > Happy New Year. May it be enumerable. > > And we're back again. The RFC has been updated with a steady stream of > smaller improvements based on feedback and testing, and is now in its Final > Form(tm) (we think). The only major change worth noting is that we renamed > things. :-) > > An enum with no scalar backing is now called a Pure Enum, made up of Pure > Cases. One that does have backing values is called a Backed Enum, made up > of Backed Cases. That change is mainly to allow for future expansion to > non-scalar backing static values, should the use case arise. Reflection > was also reworked a bit to make it more logical. > > https://wiki.php.net/rfc/enumerations > > At this point, Ilija and I consider the RFC done and ready for a vote. > Baring any major issues being brought up, we plan to start the vote in the > first half of next week, probably Tuesday-ish. If you have any other bug > reports or tweaks, please speak now or forever hold your patches. > > --Larry Garfield > >
Hi, Maybe IterableEnum can actually be just Enum. As you are not allowed to implement it and/or define it, wouldn't it work? That's how it's named internally in Java, not that it would matter but sometimes people forget it's just syntactic sugar there as well ( https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html): public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable. Also in the interface I think you can include the name property, similarly with how you did in BackedEnum interface. https://wiki.php.net/rfc/property_accessors will probably allow it to be clearly defined at some point. A bit it bothers me that backed enums are very easy to implement even without the special case and just a simple Enum would be fine. I mean even if you will have a backed enum, with would be simple and probably the need will come at some point to have also a public function getLegacyValue(): string and a public static function fromLegacyValue(string $value): Enum. But yes, using backed values is a common pattern so I'm guessing it's a valuable important use case. For storing in a database purpose, property name can be used directly, I think. It would nice to have in the rfc the recommended way to retrieve the Enum, given that you know the name. I'm guessing that would be Suit::$name; Thanks, Alex

Larry Garfield

5 years ago
On Mon, Feb 1, 2021, at 11:48 AM, Alexandru Pătrănescu wrote:
> > https://wiki.php.net/rfc/enumerations > > > > At this point, Ilija and I consider the RFC done and ready for a vote. > > Baring any major issues being brought up, we plan to start the vote in the > > first half of next week, probably Tuesday-ish. If you have any other bug > > reports or tweaks, please speak now or forever hold your patches. > > > > --Larry Garfield > > > > > Hi, > > Maybe IterableEnum can actually be just Enum. As you are not allowed to > implement it and/or define it, wouldn't it work? That's how it's named > internally in Java, not that it would matter but sometimes people forget > it's just syntactic sugar there as well ( > https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html): public > abstract class Enum<E extends Enum<E>> implements Comparable<E>, > Serializable.
We tried that. The `enum` keyword precludes any class or interface being called `Enum`, even internally.
> Also in the interface I think you can include the name property, similarly > with how you did in BackedEnum interface. > https://wiki.php.net/rfc/property_accessors will probably allow it to be > clearly defined at some point. > > A bit it bothers me that backed enums are very easy to implement even > without the special case and just a simple Enum would be fine. > I mean even if you will have a backed enum, with would be simple and > probably the need will come at some point to have also a public function > getLegacyValue(): string and a public static function > fromLegacyValue(string $value): Enum. > But yes, using backed values is a common pattern so I'm guessing it's a > valuable important use case.
It's more about standardizing the API. An ORM can rely on a backed enum always exposing its "value to save to the DB" at ->value, rather than it sometimes being ->value(), sometimes ->legacyValue(), sometimes ->asInt(), etc.
> For storing in a database purpose, property name can be used directly, I > think. > It would nice to have in the rfc the recommended way to retrieve the Enum, > given that you know the name. > I'm guessing that would be Suit::$name;
That doesn't work for referencing a constant; it gets read as a static property. That's a more general syntactic question for PHP, and one we're not going to solve here. :-) Really, ->name is more an implementation artifact. If you want to have a primitive to pass around, for whatever reason, that's what a backed enum is for. --Larry Garfield

David Gebler

5 years ago
> We tried that. The `enum` keyword precludes any class or interface being
called `Enum`, even internally. Enumerable, Enumerated, Enumerator? On Mon, Feb 1, 2021 at 7:30 PM Larry Garfield <larry@garfieldtech.com> wrote:

Alexandru Pătrănescu

5 years ago
On Mon, Feb 1, 2021 at 9:30 PM Larry Garfield <larry@garfieldtech.com> wrote:
> On Mon, Feb 1, 2021, at 11:48 AM, Alexandru Pătrănescu wrote: > > > > > https://wiki.php.net/rfc/enumerations > > > > > > At this point, Ilija and I consider the RFC done and ready for a vote. > > > Baring any major issues being brought up, we plan to start the vote in > the > > > first half of next week, probably Tuesday-ish. If you have any other > bug > > > reports or tweaks, please speak now or forever hold your patches. > > > > > > --Larry Garfield > > > > > > > > Hi, > > > > Maybe IterableEnum can actually be just Enum. As you are not allowed to > > implement it and/or define it, wouldn't it work? That's how it's named > > internally in Java, not that it would matter but sometimes people forget > > it's just syntactic sugar there as well ( > > https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html): public > > abstract class Enum<E extends Enum<E>> implements Comparable<E>, > > Serializable. > > We tried that. The `enum` keyword precludes any class or interface being > called `Enum`, even internally. > >
I see. I made an assumption based on how I thought the situation should be. Maybe that should not be impossible but I'm a bit further from understanding how that could be fixed.
> For storing in a database purpose, property name can be used directly, I > > think. > > It would nice to have in the rfc the recommended way to retrieve the > Enum, > > given that you know the name. > > I'm guessing that would be Suit::$name; > > That doesn't work for referencing a constant; it gets read as a static > property. That's a more general syntactic question for PHP, and one we're > not going to solve here. :-) Really, ->name is more an implementation > artifact. If you want to have a primitive to pass around, for whatever > reason, that's what a backed enum is for. > >
Yes, you are right. It would work however with constant(Suit::class . '::' . $name) so someone could very easily define a static method for that and serialization for a database is back for not backed enums. Maybe there would be a way to remove the name public property completely to avoid missusages? So a "pure enum" should only be used in code as property, parameter, return value types, reading from the constant and compared with identity operator (match included). Whenever that enum needs to be stored as a value as user defined int/string, a backed enum should be used that has a public read-only value property and a public static from method. Yes... sounds good. Alex

Larry Garfield

5 years ago
On Fri, Jan 29, 2021, at 11:15 AM, Larry Garfield wrote:
> And we're back again. The RFC has been updated with a steady stream of > smaller improvements based on feedback and testing, and is now in its > Final Form(tm) (we think). The only major change worth noting is that > we renamed things. :-) > > An enum with no scalar backing is now called a Pure Enum, made up of > Pure Cases. One that does have backing values is called a Backed Enum, > made up of Backed Cases. That change is mainly to allow for future > expansion to non-scalar backing static values, should the use case > arise. Reflection was also reworked a bit to make it more logical. > > https://wiki.php.net/rfc/enumerations > > At this point, Ilija and I consider the RFC done and ready for a vote. > Baring any major issues being brought up, we plan to start the vote in > the first half of next week, probably Tuesday-ish. If you have any > other bug reports or tweaks, please speak now or forever hold your > patches. > > --Larry Garfield
Based on feedback from Nikita we've made three last changes to Naming Things(tm). 1) IterableEnum is back to "UnitEnum", which is the superset of Pure enums and Backed enums. 2) The reflection classes were renamed accordingly. 3) We clarified that tryFrom() will behave "as you'd expect" if passed an incorrect type, given strong vs weak mode. And a few other inconsequential word tightenings. Baring any other revelations, voting starts tomorrow. --Larry Garfield