Re: [PHP-DEV] [IDEA] allow extending enum
Hi Davey On 29.03.23 16:31, Davey Shafik wrote: The issue of contention here is that inheritance isn't the correct mechanism for reusing enum definitions to expand on them. What about allowing "use" syntax like traits? enum HttpErrors { case HTTP_STATUS_400; case HTTP_STATUS_401; … } enum ApiErrors { use HttpErrors; case JsonParserError; case GraphQLParserError; } Now you have ApiErrors::HTTP_STATUS_400 etc without ApiErrors being is-a HttpErrors. This would still require making ApiErrors a supertype of HttpErrors so that a parameter typed as ApiErrors accepts a HttpErrors::HTTP_STATUS_400 value. This essentially mimicks implicit multiple inheritance. The fact that these are implicit (for HttpErrors, that is) makes this quite confusing, and difficult to implement technically (usually classes are "locked" after linking). I would much prefer the previously suggested approaches (using algebraic data types, a.k.a. tagged unions, or union types of two enums). Ilija -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
Hi Rokas, On Thu, 30 Mar 2023 at 08:36, Rokas Šleinius wrote: > > On Wed, 29 Mar 2023 at 18:40, Larry Garfield wrote: > > > > 1) Please don't top-post. > > Am I doing it right now? I've never been on a mailing list. You're replying in the preferred manner, yes. Quoting only the needed amount and putting replies underneath. Though deleting all non-relevant text is also good. Have two possibly useful guidelines: * Mailing list etiquette guidelines - https://phpopendocs.com/internals/mailing_list * Email etiquette for people new to newsgroups / PHP internals - https://phpopendocs.com/internals/mailing_list_for_younguns which might get copied across to php.net at some point... > In fact, I was too young and missed out on BBS as well, and now using > this laughably archaic collaboration method, receiving mostly > stone-harsh replies generously sprinkled with genius insights, having > to quite literally hack a registration form as one of the several > required steps for contribution... Wow this is a really remarkable > experience for me :D Well, it builds character. And as soon as someone suggests something that is obviously better, and not just replacing one set of problems with another, the discussions might move. > I am really impressed how elegant and actually genius the enum > implementation is. So well rounded, minimal, yet creates so much > value! And it only took the PHP project about 20 years of discussion to get an agreeable implementation... cheers Dan Ack -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
Hi On 3/29/23 16:31, Davey Shafik wrote: The issue of contention here is that inheritance isn't the correct mechanism for reusing enum definitions to expand on them. What about allowing "use" syntax like traits? enum HttpErrors { case HTTP_STATUS_400; case HTTP_STATUS_401; … } I understand that this is just an example, but I'd like to note that using an enum for HTTP status codes would likely be a misuse. Valid HTTP status codes are all three-digit numbers in 1xx to 5xx and HTTP error codes are everything 4xx and 5xx. HTTP status code are specifically designed for you to be able to handle them generically based on the first digit, even if you don't know about the specific code. enum ApiErrors { use HttpErrors; case JsonParserError; case GraphQLParserError; } Now you have ApiErrors::HTTP_STATUS_400 etc without ApiErrors being is-a HttpErrors. Including all the cases of another enum is not unlikely to result in conflicts, because the enum name effectively acts as a namespace with regard to the enum cases. Staying with the HttpErrors enum for sake of the example, you'd rather use `case Forbidden` instead of `case HTTP_STATUS_403`, because `HttpErrors::HTTP_STATUS_403` redundantly includes the 'HTTP'. Mark's example of DatabaseError would possibly also have a `case Forbidden` in case your database password is incorrect or in case you may not access the queried table or whatever. Instead it would likely be preferable to get tagged unions (https://wiki.php.net/rfc/tagged_unions) and then nest the errors, like commonly done in Rust: enum ApiErrors { case HttpRequest(HttpErrors $httpError); case JsonParsing; case GraphQlParsing; } And then `$foo = ApiErrors::HttpRequest(HttpErrors::Forbidden);`. The Exception equivalent would be leveraging the `$previous` value to provide the context. Best regards Tim Düsterhus -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
On Wed, 29 Mar 2023 at 18:40, Larry Garfield wrote: > > On Wed, Mar 29, 2023, at 2:25 PM, Rokas Šleinius wrote: > > First, I'm pretty sure I agree now that enums should *not* be > > `extend`-ed as regular classes, there's a fundamental difference as > > you state - the extended enum is a fundamentally different THING as it > > can hold different values - when an enum's sole purpose is to hold one > > of the set of values. > > > > That is to say - for most implementations that's the desired case. > > > > I'll refer to the solution to the OP problem as ECS, Error Code System. > > > > For ECS, the most elegant, but also the only, solution I can come up > > with - is the kind of enum that you also suggest, a `base-enum`. > > `abstract enum`? > > > > Let me try again to describe the problem that I came here with that is > > unsolvable by union types: > > > > 1. ECS is a *generic* component. I want to plug it into any working > > system (like an existing cms). > > 2. Invoking it must provide a unique ErrorCode. The existing cms has > > to create an enum that will be accepted as `type: ErrorCode`. It > > cannot edit the existing ErrorCode as it is part of ECS and not the > > cms. > > 3. There is NO special handling for the custom ErrorCode's that the > > users of ECS create. We are just using the enums inferred value. > > > > I will keep processing this situation further, but for now it seems to me > > like > > > > 1. Extending enums is fundamentally flawed. > > 2. Basing enums off of other enums has valid usage scenarios. > > > > In that case, and, again, this needs way more thought to it, it's not > > such a "generic way forward" that seemed to me at first and might only > > provide marginal value at the cost of type complexity and that is most > > probably, unfortunately, not worth it... > > > > (unless `abstract enum` might make sense, but my brain needs some time > > off of this problem for now) > > > > Thank you for such a thought out discussion, everyone! > > 1) Please don't top-post. > > 2) Some good reading material for this topic, both on enums and on error > handling: > > https://peakd.com/hive-168588/@crell/on-the-use-of-enums > https://peakd.com/hive-168588/@crell/much-ado-about-null > > They're not short, but that's because they are complete. :-) > > 3) In the error code case, the answer is to leverage the fact that enums *do* > support interfaces. > > interface ErrorCode > { > public function message(): string; > } > > enum CommonErrors: string implements ErrorCode > { > case YourFault = 'You screwed up'; > case OurFault = 'We screwed up'; > > public function message(): string > { > return $this->value; > } > } > > readonly class SomeoneElseError implements ErrorCode > { > public function __construct(privateUser $atFault) {} > > public function message(): string > { > return sprintf('%s screwed up', $this->atFault->name()); > } > } > > Now an error handling system can type against the ErrorCode interface, common > errors have trivially simple enum values you can use, but you can also make > your own. In this case, you're *not* using an enum as a limited-set; you're > taking advantage of it being a way to predefine singleton objects. I would > consider this a completely valid use of enums, and actually do something very > similar in my serialization library: > > https://github.com/Crell/Serde/blob/master/src/Renaming/RenamingStrategy.php > https://github.com/Crell/Serde/blob/master/src/Renaming/Cases.php > https://github.com/Crell/Serde/blob/master/src/Renaming/Prefix.php > > 4) As others have said, extending enums is a bad idea with lots of reasons > it's a bad idea, both conceptual and pragmatic. However, I would be open to > discussing a `use` for enums to import cases from one enum into another. > > There's two concerns with that to consider: > > A) What happens to methods on the imported enum? Are they pulled in as well? > Do interface definitions carry over? I don't know. > > B) The long-term goal is to expand Enums to include associated values > (https://wiki.php.net/rfc/tagged_unions, although that's a bit out of date > now so don't take it as a roadmap). How would `use`-ing an enum within > another enum affect that? I have no idea, off hand. > > --Larry Garfield > 1) Please don't top-post. Am I doing it right now? I've never been on a mailing list. In fact, I was too young and missed out on BBS as well, and now using this laughably archaic collaboration method, receiving mostly stone-harsh replies generously sprinkled with genius insights, having to quite literally hack a registration form as one of the several required steps for contribution... Wow this is a really remarkable experience for me :D > 2) Some good reading material for this topic, both on enums and on error > handling: Thanks, noted, bookmarked, subscribed. BTW the ECS example is only dealing with Errors because it's a concept I thought people would grasp and relate to
Re: [PHP-DEV] [IDEA] allow extending enum
On Wed, Mar 29, 2023, at 4:28 PM, Mark Baker wrote: > Another benefit of this approach would be to allow the inclusion of > multiple enums, in the same way that we can include multiple traits. > > > enum HttpErrors { > > case HTTP_STATUS_400; > > case HTTP_STATUS_401; > > … > > } > > enum DatabaseErrors { > > case RECORD_NOT_FOUND; > case DATABASE_NOT_AVAILABLE; > > … > > } > > > > enum ApiErrors { > > use HttpErrors, DatabaseErrors; > > > > case JsonParserError; > > case GraphQLParserError; > > } All that said, we could get the same functionality with fewer questions if we just finally got around to introducing type definitions. type ApiError = DatabaseError|HttpError; function handleErrors(ApiError $error) {...} That would obviate the need for a lot of workarounds. --Larry Garfield -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
On 29/03/2023 16:43, Mark Baker wrote: On 29/03/2023 16:31, Davey Shafik wrote: On Mar 29, 2023, at 06:56, Rowan Tommins wrote: On Wed, 29 Mar 2023 at 14:22, Rokas Šleinius wrote: Ok so I am trying to find the argumentation here: This is by design. The point of enums is to be limiting. This is clearly an assumption. That statement is not in the docs or RFC, nor such an oversimplification makes logical sense to me, but maybe you have a source to back it up..? From a theory point of view, *any* type definition is about limiting allowed values. The difference between "mixed" and "integer" is that "mixed" allows both 'Hello' and 42, but "integer" does not - it defines a tighter limit. In the same way, saying "I accept an error code" means "I accept an error code *and nothing else*" - the definition of what is and what isn't an "error code" is deliberately a *limit* on the values that you accept. Re: problem in the OP You are approaching the design of this in a fundamentally wrong way. The described problem is dealing with error *codes* not *types* An enum is a type, so that's why George was talking about types. He was making the same point, in different words, as I did: the relationship between types implied by the keyword "extends" is not the relationship you want in this case. The set in question is *finite*, as you say of course, but much larger and different in principle than in the solution you are proposing. Problem in OP is looking for a way for end users to keep adding new domains to the error codes passed to the parent system to be handled. This is the key point - if you pass an error code that didn't previously exist, the existing system *won't* be able to handle it. That's why enums are inherently restrictive: the system wants to be able to say "this is the list of error codes I understand, don't give me anything else". If you have a system that accepts the new codes as well as the old ones, you can use a union type declaration as George says: enum AdditionalErrorCode { case Code_456; } class ExtendedErrorHandler extends StandardErrorHandler { public function handle(StandardErrorCode|AdditionalErrorCode $code) { switch ( $code ) { case AdditionalErrorCode::Code_456: // handling for new code goes here break; default: // assert($code instanceof StandardErrorCode); parent::handle($code); break; } } } The issue of contention here is that inheritance isn't the correct mechanism for reusing enum definitions to expand on them. What about allowing "use" syntax like traits? enum HttpErrors { case HTTP_STATUS_400; case HTTP_STATUS_401; … } enum ApiErrors { use HttpErrors; case JsonParserError; case GraphQLParserError; } Now you have ApiErrors::HTTP_STATUS_400 etc without ApiErrors being is-a HttpErrors. That would make a lot of sense, and I was just having similar thoughts myself. ApiErrors isn't the same enum, or an extension of HttpErrors: it's a new Enum, but it does include all HttpErrors cases (and methods) without the need to duplicate code, and any new cases that are subsequently added to HttpErrors will automatically be incorporated in ApiErrors. Another benefit of this approach would be to allow the inclusion of multiple enums, in the same way that we can include multiple traits. enum HttpErrors { case HTTP_STATUS_400; case HTTP_STATUS_401; … } enum DatabaseErrors { case RECORD_NOT_FOUND; case DATABASE_NOT_AVAILABLE; … } enum ApiErrors { use HttpErrors, DatabaseErrors; case JsonParserError; case GraphQLParserError; } -- Mark Baker
Re: [PHP-DEV] [IDEA] allow extending enum
Hi On 3/29/23 13:01, Rokas Šleinius wrote: There's a hypothetical (based on a similar real world problem that I am facing) example for the use of extending enums in the OP. And I don't suppose Tim was arguing for not allowing enum extendability, but rather contributing a real world example where users who were having some totally legal fun hacking around with internal PHP code would break said internal PHP code. I'm seeing that you already thought about the topic more based on the other responses. But for the record: I *was* arguing for not allowing enum extendability. Which may or may not be a big deal from my uneducated point of view, just saying :) And all could be prevented just by adding a `final` statement to the definition of `IntervalBoundary`. Requiring folks to add 'final' to their existing enums for them to behave as expected would be considered a breaking change. Best regards Tim Düsterhus -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
Hi On 3/29/23 12:41, Christian Schneider wrote: And to give a specific example from PHP 8.3: As part of the Randomizer additions RFC (https://wiki.php.net/rfc/randomizer_additions), PHP 8.3 got its first "natively included" enum (\Random\IntervalBoundary). This enum works together with the new Randomizer::getFloat() method to specify if the given min and max value may be returned or not. The Randomizer::getFloat() method internally includes switch statement that chooses the implementation based on the IntervalBoundary value given. If a user would be able to extend the IntervalBoundary enum, the method would not be able to make sense of it. The IntervalBoundary enum completely enumerates all four possible combinations for the two possible states for each of the two boundaries (2*2 = 4). By definition there is no other valid value. I do understand the reason behind making Enums final and your example illustrates the point very well. I still have a question I already asked in a different context: If there ever is the need to add a case to an Enum, was there any thought put into making this possible? Or is this categorically ruled out when using Enums? Adding a case to an enum would certainly be considered a breaking change for folks that rely on having exhaustively handled all possible cases. Let’s look at a very, very hypothetical example: Imagine a Randomizer is much, much slower for certain boundaries and it is decided that some programs do not care about Closed/Open but instead care more about speed. So they would want to use something like IntervalBoundary::Fastest. In this specific hypothetical example, the fastest case would be one of the existing 4 cases - an alias if you want to call it that. Defining an alias is possible without breaking compatibility by leveraging class constants: https://3v4l.org/tT0Ho Alternatively a method "public static function getFastest(): self" would be possible. As far as I understand such an addition would *never* be possible, right? This means people defining Enums have to be very very certain that no one will ever want another value, right? I believe the short answer to the last question is "Yes". Best regards Tim Düsterhus -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
On Wed, Mar 29, 2023, at 2:25 PM, Rokas Šleinius wrote: > First, I'm pretty sure I agree now that enums should *not* be > `extend`-ed as regular classes, there's a fundamental difference as > you state - the extended enum is a fundamentally different THING as it > can hold different values - when an enum's sole purpose is to hold one > of the set of values. > > That is to say - for most implementations that's the desired case. > > I'll refer to the solution to the OP problem as ECS, Error Code System. > > For ECS, the most elegant, but also the only, solution I can come up > with - is the kind of enum that you also suggest, a `base-enum`. > `abstract enum`? > > Let me try again to describe the problem that I came here with that is > unsolvable by union types: > > 1. ECS is a *generic* component. I want to plug it into any working > system (like an existing cms). > 2. Invoking it must provide a unique ErrorCode. The existing cms has > to create an enum that will be accepted as `type: ErrorCode`. It > cannot edit the existing ErrorCode as it is part of ECS and not the > cms. > 3. There is NO special handling for the custom ErrorCode's that the > users of ECS create. We are just using the enums inferred value. > > I will keep processing this situation further, but for now it seems to me like > > 1. Extending enums is fundamentally flawed. > 2. Basing enums off of other enums has valid usage scenarios. > > In that case, and, again, this needs way more thought to it, it's not > such a "generic way forward" that seemed to me at first and might only > provide marginal value at the cost of type complexity and that is most > probably, unfortunately, not worth it... > > (unless `abstract enum` might make sense, but my brain needs some time > off of this problem for now) > > Thank you for such a thought out discussion, everyone! 1) Please don't top-post. 2) Some good reading material for this topic, both on enums and on error handling: https://peakd.com/hive-168588/@crell/on-the-use-of-enums https://peakd.com/hive-168588/@crell/much-ado-about-null They're not short, but that's because they are complete. :-) 3) In the error code case, the answer is to leverage the fact that enums *do* support interfaces. interface ErrorCode { public function message(): string; } enum CommonErrors: string implements ErrorCode { case YourFault = 'You screwed up'; case OurFault = 'We screwed up'; public function message(): string { return $this->value; } } readonly class SomeoneElseError implements ErrorCode { public function __construct(privateUser $atFault) {} public function message(): string { return sprintf('%s screwed up', $this->atFault->name()); } } Now an error handling system can type against the ErrorCode interface, common errors have trivially simple enum values you can use, but you can also make your own. In this case, you're *not* using an enum as a limited-set; you're taking advantage of it being a way to predefine singleton objects. I would consider this a completely valid use of enums, and actually do something very similar in my serialization library: https://github.com/Crell/Serde/blob/master/src/Renaming/RenamingStrategy.php https://github.com/Crell/Serde/blob/master/src/Renaming/Cases.php https://github.com/Crell/Serde/blob/master/src/Renaming/Prefix.php 4) As others have said, extending enums is a bad idea with lots of reasons it's a bad idea, both conceptual and pragmatic. However, I would be open to discussing a `use` for enums to import cases from one enum into another. There's two concerns with that to consider: A) What happens to methods on the imported enum? Are they pulled in as well? Do interface definitions carry over? I don't know. B) The long-term goal is to expand Enums to include associated values (https://wiki.php.net/rfc/tagged_unions, although that's a bit out of date now so don't take it as a roadmap). How would `use`-ing an enum within another enum affect that? I have no idea, off hand. --Larry Garfield -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
On Wed, 29 Mar 2023 at 15:26, Rokas Šleinius wrote: > Invoking it must provide a unique ErrorCode. The concept of "uniqueness" only makes sense within a certain context - the CMS easily create these two enums: enum HttpErrorCode: int extends ErrorCode { case NotFound = 404; } enum SQLErrorCode: int extends ErrorCode { case BadColumnRef = 404; } If ECS accepts both HttpErrorCode::NotFound and SQLErrorCode::BadColumnRef, and looks at the integer value, they will both be 404. Are they still "unique" in any useful way? The existing cms has > to create an enum that will be accepted as `type: ErrorCode`. > [...] > There is NO special handling for the custom ErrorCode's that the > users of ECS create. I don't understand this pair of requirements: if ECS doesn't have handling based on the value, why does it care what type it is? If it cares that it can turn it into an integer, it can just request an integer directly, or an object implementing an appropriate interface: interface ErrorValueInterface { public function getCode(): int; } >From the description given, it seems like enums are sinmply the wrong tool for the job. Regards, -- Rowan Tommins [IMSoP]
Re: [PHP-DEV] [IDEA] allow extending enum
On 29/03/2023 16:31, Davey Shafik wrote: On Mar 29, 2023, at 06:56, Rowan Tommins wrote: On Wed, 29 Mar 2023 at 14:22, Rokas Šleinius wrote: Ok so I am trying to find the argumentation here: This is by design. The point of enums is to be limiting. This is clearly an assumption. That statement is not in the docs or RFC, nor such an oversimplification makes logical sense to me, but maybe you have a source to back it up..? From a theory point of view, *any* type definition is about limiting allowed values. The difference between "mixed" and "integer" is that "mixed" allows both 'Hello' and 42, but "integer" does not - it defines a tighter limit. In the same way, saying "I accept an error code" means "I accept an error code *and nothing else*" - the definition of what is and what isn't an "error code" is deliberately a *limit* on the values that you accept. Re: problem in the OP You are approaching the design of this in a fundamentally wrong way. The described problem is dealing with error *codes* not *types* An enum is a type, so that's why George was talking about types. He was making the same point, in different words, as I did: the relationship between types implied by the keyword "extends" is not the relationship you want in this case. The set in question is *finite*, as you say of course, but much larger and different in principle than in the solution you are proposing. Problem in OP is looking for a way for end users to keep adding new domains to the error codes passed to the parent system to be handled. This is the key point - if you pass an error code that didn't previously exist, the existing system *won't* be able to handle it. That's why enums are inherently restrictive: the system wants to be able to say "this is the list of error codes I understand, don't give me anything else". If you have a system that accepts the new codes as well as the old ones, you can use a union type declaration as George says: enum AdditionalErrorCode { case Code_456; } class ExtendedErrorHandler extends StandardErrorHandler { public function handle(StandardErrorCode|AdditionalErrorCode $code) { switch ( $code ) { case AdditionalErrorCode::Code_456: // handling for new code goes here break; default: // assert($code instanceof StandardErrorCode); parent::handle($code); break; } } } The issue of contention here is that inheritance isn't the correct mechanism for reusing enum definitions to expand on them. What about allowing "use" syntax like traits? enum HttpErrors { case HTTP_STATUS_400; case HTTP_STATUS_401; … } enum ApiErrors { use HttpErrors; case JsonParserError; case GraphQLParserError; } Now you have ApiErrors::HTTP_STATUS_400 etc without ApiErrors being is-a HttpErrors. That would make a lot of sense, and I was just having similar thoughts myself. ApiErrors isn't the same enum, or an extension of HttpErrors: it's a new Enum, but it does include all HttpErrors cases (and methods) without the need to duplicate code, and any new cases that are subsequently added to HttpErrors will automatically be incorporated in ApiErrors. -- Mark Baker -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
> On Mar 29, 2023, at 06:56, Rowan Tommins wrote: > > On Wed, 29 Mar 2023 at 14:22, Rokas Šleinius wrote: > >> Ok so I am trying to find the argumentation here: >>> This is by design. >>> The point of enums is to be limiting. >> This is clearly an assumption. That statement is not in the docs or >> RFC, nor such an oversimplification makes logical sense to me, but >> maybe you have a source to back it up..? > > > From a theory point of view, *any* type definition is about limiting > allowed values. The difference between "mixed" and "integer" is that > "mixed" allows both 'Hello' and 42, but "integer" does not - it defines a > tighter limit. > > In the same way, saying "I accept an error code" means "I accept an error > code *and nothing else*" - the definition of what is and what isn't an > "error code" is deliberately a *limit* on the values that you accept. > > > >>> Re: problem in the OP >>> You are approaching the design of this in a fundamentally wrong way. >> The described problem is dealing with error *codes* not *types* > > > > An enum is a type, so that's why George was talking about types. He was > making the same point, in different words, as I did: the relationship > between types implied by the keyword "extends" is not the relationship you > want in this case. > > > >> The set in question is *finite*, as you say of course, but much larger >> and different in principle than in the solution you are proposing. >> Problem in OP is looking for a way for end users to keep adding new >> domains to the error codes passed to the parent system to be handled. > > > This is the key point - if you pass an error code that didn't previously > exist, the existing system *won't* be able to handle it. > > That's why enums are inherently restrictive: the system wants to be able to > say "this is the list of error codes I understand, don't give me anything > else". > > If you have a system that accepts the new codes as well as the old ones, > you can use a union type declaration as George says: > > enum AdditionalErrorCode { > case Code_456; > } > > class ExtendedErrorHandler extends StandardErrorHandler { > public function handle(StandardErrorCode|AdditionalErrorCode $code) { >switch ( $code ) { >case AdditionalErrorCode::Code_456: > // handling for new code goes here >break; >default: >// assert($code instanceof StandardErrorCode); >parent::handle($code); >break; > } > } > } The issue of contention here is that inheritance isn't the correct mechanism for reusing enum definitions to expand on them. What about allowing "use" syntax like traits? enum HttpErrors { case HTTP_STATUS_400; case HTTP_STATUS_401; … } enum ApiErrors { use HttpErrors; case JsonParserError; case GraphQLParserError; } Now you have ApiErrors::HTTP_STATUS_400 etc without ApiErrors being is-a HttpErrors. - Davey -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
First, I'm pretty sure I agree now that enums should *not* be `extend`-ed as regular classes, there's a fundamental difference as you state - the extended enum is a fundamentally different THING as it can hold different values - when an enum's sole purpose is to hold one of the set of values. That is to say - for most implementations that's the desired case. I'll refer to the solution to the OP problem as ECS, Error Code System. For ECS, the most elegant, but also the only, solution I can come up with - is the kind of enum that you also suggest, a `base-enum`. `abstract enum`? Let me try again to describe the problem that I came here with that is unsolvable by union types: 1. ECS is a *generic* component. I want to plug it into any working system (like an existing cms). 2. Invoking it must provide a unique ErrorCode. The existing cms has to create an enum that will be accepted as `type: ErrorCode`. It cannot edit the existing ErrorCode as it is part of ECS and not the cms. 3. There is NO special handling for the custom ErrorCode's that the users of ECS create. We are just using the enums inferred value. I will keep processing this situation further, but for now it seems to me like 1. Extending enums is fundamentally flawed. 2. Basing enums off of other enums has valid usage scenarios. In that case, and, again, this needs way more thought to it, it's not such a "generic way forward" that seemed to me at first and might only provide marginal value at the cost of type complexity and that is most probably, unfortunately, not worth it... (unless `abstract enum` might make sense, but my brain needs some time off of this problem for now) Thank you for such a thought out discussion, everyone! On Wed, 29 Mar 2023 at 16:56, Rowan Tommins wrote: > > On Wed, 29 Mar 2023 at 14:22, Rokas Šleinius wrote: > > > Ok so I am trying to find the argumentation here: > > > > >This is by design. > > >The point of enums is to be limiting. > > > > This is clearly an assumption. That statement is not in the docs or > > RFC, nor such an oversimplification makes logical sense to me, but > > maybe you have a source to back it up..? > > > > > From a theory point of view, *any* type definition is about limiting > allowed values. The difference between "mixed" and "integer" is that > "mixed" allows both 'Hello' and 42, but "integer" does not - it defines a > tighter limit. > > In the same way, saying "I accept an error code" means "I accept an error > code *and nothing else*" - the definition of what is and what isn't an > "error code" is deliberately a *limit* on the values that you accept. > > > > > > > >Re: problem in the OP > > >You are approaching the design of this in a fundamentally wrong way. > > > > The described problem is dealing with error *codes* not *types* > > > > An enum is a type, so that's why George was talking about types. He was > making the same point, in different words, as I did: the relationship > between types implied by the keyword "extends" is not the relationship you > want in this case. > > > > > The set in question is *finite*, as you say of course, but much larger > > and different in principle than in the solution you are proposing. > > Problem in OP is looking for a way for end users to keep adding new > > domains to the error codes passed to the parent system to be handled. > > > > > This is the key point - if you pass an error code that didn't previously > exist, the existing system *won't* be able to handle it. > > That's why enums are inherently restrictive: the system wants to be able to > say "this is the list of error codes I understand, don't give me anything > else". > > If you have a system that accepts the new codes as well as the old ones, > you can use a union type declaration as George says: > > enum AdditionalErrorCode { > case Code_456; > } > > class ExtendedErrorHandler extends StandardErrorHandler { > public function handle(StandardErrorCode|AdditionalErrorCode $code) { > switch ( $code ) { > case AdditionalErrorCode::Code_456: > // handling for new code goes here > break; > default: > // assert($code instanceof StandardErrorCode); > parent::handle($code); > break; > } > } > } > > Regards, > -- > Rowan Tommins > [IMSoP] -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
On Wed, 29 Mar 2023 at 14:22, Rokas Šleinius wrote: > Ok so I am trying to find the argumentation here: > > >This is by design. > >The point of enums is to be limiting. > > This is clearly an assumption. That statement is not in the docs or > RFC, nor such an oversimplification makes logical sense to me, but > maybe you have a source to back it up..? > >From a theory point of view, *any* type definition is about limiting allowed values. The difference between "mixed" and "integer" is that "mixed" allows both 'Hello' and 42, but "integer" does not - it defines a tighter limit. In the same way, saying "I accept an error code" means "I accept an error code *and nothing else*" - the definition of what is and what isn't an "error code" is deliberately a *limit* on the values that you accept. > > >Re: problem in the OP > >You are approaching the design of this in a fundamentally wrong way. > > The described problem is dealing with error *codes* not *types* An enum is a type, so that's why George was talking about types. He was making the same point, in different words, as I did: the relationship between types implied by the keyword "extends" is not the relationship you want in this case. > The set in question is *finite*, as you say of course, but much larger > and different in principle than in the solution you are proposing. > Problem in OP is looking for a way for end users to keep adding new > domains to the error codes passed to the parent system to be handled. > This is the key point - if you pass an error code that didn't previously exist, the existing system *won't* be able to handle it. That's why enums are inherently restrictive: the system wants to be able to say "this is the list of error codes I understand, don't give me anything else". If you have a system that accepts the new codes as well as the old ones, you can use a union type declaration as George says: enum AdditionalErrorCode { case Code_456; } class ExtendedErrorHandler extends StandardErrorHandler { public function handle(StandardErrorCode|AdditionalErrorCode $code) { switch ( $code ) { case AdditionalErrorCode::Code_456: // handling for new code goes here break; default: // assert($code instanceof StandardErrorCode); parent::handle($code); break; } } } Regards, -- Rowan Tommins [IMSoP]
Re: [PHP-DEV] [IDEA] allow extending enum
Ok so I am trying to find the argumentation here: >This is by design. >The point of enums is to be limiting. This is clearly an assumption. That statement is not in the docs or RFC, nor such an oversimplification makes logical sense to me, but maybe you have a source to back it up..? >Re: problem in the OP >You are approaching the design of this in a fundamentally wrong way. The described problem is dealing with error *codes* not *types*, which means that each error is assigned a new *code*. We are dealing with anticipated errors here, things like validation, data integrity, 3rd party API breakage etc. e.g.: user gets code 456 trying to ship out package, it says package not paid, user says he looked it up, its paid. The developer has a very concrete lead to the problem - lookup usages of `ErrorCodes::Code_456`. The set in question is *finite*, as you say of course, but much larger and different in principle than in the solution you are proposing. Problem in OP is looking for a way for end users to keep adding new domains to the error codes passed to the parent system to be handled. The actual, real requirements are: 1. Ease of management of codes: simplicity of adding *new* codes to the set, easy lookup, simplicity of making sure a code is not used elsewhere before using it. All perfectly fulfilled by enums. 2. Reusing the parent system for different modules, using the parent system in two different packages I want to opensource. Open Sourcing the parent system.. Rowan Tommins is right on the target however, that is amazing feedback for the desired functionality, excellent distillment of logic and all correct points! I will take some time to think about that. On Wed, 29 Mar 2023 at 15:30, G. P. B. wrote: > > On Wed, 29 Mar 2023 at 09:31, Rokas Šleinius wrote: >> >> Enums were a very useful addition to PHP, however one aspect of them is >> neither >> explicitly documented - or seemingly even talked about. >> >> Enums were implemented as final so they cannot be extended nor can extend >> anything else. > > > This is by design. > Enumerations are in type theory parlance sum types. > Objects in PHP, in general, are what are called product types. > >> >> From a user perspective it's surprising - and actually limiting. > > > The point of enums is to be limiting. > >> >> USAGE EXAMPLE: >> I am making an error management system: each error presented to the user >> must have a unique code visible. >> >> ```php >> class SystemError >> { >> public function __construct( >> private string $errorText, >> private ErrorCode $code >> ) { >> } >> >> public function __toString(): >> { >> return $this->errorText . ' ' . $this->code->toString(); >> } >> } >> // ... >> >> enum ErrorCode >> { >> case Code_1; >> case Code_2; >> >> public function toString(): string >> { >> return 'Error code:' . substr($this->name, strlen('Code_')); >> } >> } >> ``` >> >> Now I want to modify it to support different modules with different >> namespaces for >> errors, e.g. an ApiError, simple enough: >> >> ```php >> enum BaseErrorCode >> { >> // ... >> } >> >> enum ErrorCode extends BaseErrorCode >> { >> case Code_1; >> case Code_2; >> >> // ... >> } >> >> enum ApiErrorCode extends BaseErrorCode { >> // ... >> function toString(): string >> { >> return 'Error code:API-' . substr($this->name, strlen('Code_')); >> } >> } >> ``` >> >> This results in a syntax error. > > > You are approaching the design of this in a fundamentally wrong way. > Enums, as sum types, are meant to enclose a *finite* number of states. > If you want to expand those finite number of states, the solution is to use a > union type. > > Let's say you have those 3 enums: > > enum GenericErrors { > case ErrorType1; > case ErrorType2; > } > > enum FileErrors { > case FileErrorType1; > case FileErrorType2; > case FileErrorType3; > } > > enum NetworkErrors { > case NetworkErrorType1; > case NetworkErrorType2; > case NetworkErrorType3; > } > > And you have a function that can fail with either a generic error or a file > error then you can be explicit by having: > > function foo(): T|GenericErrors|FileErrors { /* Do something or Fail */ } > > This signature provides you with great type safety and can be checked via > static analysis that *all* error cases are handled. > Moreover, you know you don't need to handle NetworkErrors. > Extending an Enum *loses* you this type safety, because now ff you say > something returns a GenericErrors well who knows if you cover all cases > because someone might have ad-hock added a new one. > > PHP's type system is not perfect yet, but it has become powerful enough that > you can do things like what I just described. > Ideally we would have generics, and you could use a Result/Either Monad > (fancy word for "wrapper") to have a return type of Result GenericErrors|FileErrors> > > Use
Re: [PHP-DEV] [IDEA] allow extending enum
On Wed, 29 Mar 2023 at 13:30, G. P. B. wrote: > On Wed, 29 Mar 2023 at 09:31, Rokas Šleinius wrote: > > Enums were implemented as final so they cannot be extended nor can extend > > anything else. > > > > This is by design. > Enumerations are in type theory parlance sum types. > Objects in PHP, in general, are what are called product types. > I was just about to write a response along the same lines. I think the confusion comes because in Java-style OOP, the keyword "extends" actually does two things at once: 1. Takes the existing definition and adds to or over-rides parts of it 2. Takes the existing type and declares a sub-type What you actually want to do with an enum is: 1. Take the existing definition and add cases to it 2. Take the existing type and declare a *super-type* To give a concrete example of why, consider Christian's hypothetical: > Imagine a Randomizer is much, much slower for certain boundaries and it is decided that some programs do not care about Closed/Open but instead care more about speed. So they would want to use something like IntervalBoundary::Fastest. If we use the normal definition of "extends", we could say this: enum PerfIntervalBoundary extends IntervalBoundary { case Fastest; } Now we have an enum with three cases, which is what we wanted. But we've also declared a *sub-type*: we've said that "every PerfIntervalBoundary is-a IntervalBoundary". That means that anywhere that previously expected an IntervalBoundary, we can pass PerfIntervalBoundary::Fastest - but none of the existing Randomizer classes will know what to do with it! What we actually wanted to do is leave all the existing uses of IntervalBoundary alone, but create a new WeirdPerfRandomizer which accepts the new type. We wanted to list "PerfIntervalBoundary" as the input type, but still be able to pass it IntervalBoundary::Closed. In short, we want to say "every IntervalBoundary is-a PerfIntervalBoundary" - the relationship is the other way around. As George says, this can be implemented right now by using a union type constraint, like PerfIntervalBoundary|IntervalBoundary, which means "allow any case of PerfIntervalBoundary as well as any value of IntervalBoundary". If we wanted to support it more directly, we would need a new keyword to make clear that this is *not* a sub-type relationship - perhaps something like "expands", "allows", or "encompasses". Regards, -- Rowan Tommins [IMSoP]
Re: [PHP-DEV] [IDEA] allow extending enum
On Wed, 29 Mar 2023 at 09:31, Rokas Šleinius wrote: > Enums were a very useful addition to PHP, however one aspect of them is > neither > explicitly documented - or seemingly even talked about. > > Enums were implemented as final so they cannot be extended nor can extend > anything else. > This is by design. Enumerations are in type theory parlance sum types. Objects in PHP, in general, are what are called product types. > From a user perspective it's surprising - and actually limiting. > The point of enums is to be limiting. > USAGE EXAMPLE: > I am making an error management system: each error presented to the user > must have a unique code visible. > > ```php > class SystemError > { > public function __construct( > private string $errorText, > private ErrorCode $code > ) { > } > > public function __toString(): > { > return $this->errorText . ' ' . $this->code->toString(); > } > } > // ... > > enum ErrorCode > { > case Code_1; > case Code_2; > > public function toString(): string > { > return 'Error code:' . substr($this->name, strlen('Code_')); > } > } > ``` > > Now I want to modify it to support different modules with different > namespaces for > errors, e.g. an ApiError, simple enough: > > ```php > enum BaseErrorCode > { > // ... > } > > enum ErrorCode extends BaseErrorCode > { > case Code_1; > case Code_2; > > // ... > } > > enum ApiErrorCode extends BaseErrorCode { > // ... > function toString(): string > { > return 'Error code:API-' . substr($this->name, strlen('Code_')); > } > } > ``` > > This results in a syntax error. > You are approaching the design of this in a fundamentally wrong way. Enums, as sum types, are meant to enclose a *finite* number of states. If you want to expand those finite number of states, the solution is to use a union type. Let's say you have those 3 enums: enum GenericErrors { case ErrorType1; case ErrorType2; } enum FileErrors { case FileErrorType1; case FileErrorType2; case FileErrorType3; } enum NetworkErrors { case NetworkErrorType1; case NetworkErrorType2; case NetworkErrorType3; } And you have a function that can fail with either a generic error or a file error then you can be explicit by having: function foo(): T|GenericErrors|FileErrors { /* Do something or Fail */ } This signature provides you with great type safety and can be checked via static analysis that *all* error cases are handled. Moreover, you know you don't need to handle NetworkErrors. Extending an Enum *loses* you this type safety, because now ff you say something returns a GenericErrors well who knows if you cover all cases because someone might have ad-hock added a new one. PHP's type system is not perfect yet, but it has become powerful enough that you can do things like what I just described. Ideally we would have generics, and you could use a Result/Either Monad (fancy word for "wrapper") to have a return type of Result Use the type system properly instead of trying to shove everything into an "Inheritance" view of things. Union types are not "bad", they are another way of creating a sum type. (You could, with a lot of effort, create "proper" enumerations since PHP 8.0 by using union types and singleton classes.) PROPOSAL: > > Enums should be able to extend other enums. > > For a complete wishlist, add: > * abstract enums; > * enums allowed to implement interfaces; > Enums can already implement interfaces: https://3v4l.org/tKtQ0 > However since I have no experience in PHP source code, I can only > provide the test suite for a possible PR this might have :( > > Do you think this is likely to get implemented? > I dear hope so not. Breaking fundamental type theoretical assumptions is terrible. Best regards, George P. Banyard
Re: [PHP-DEV] [IDEA] allow extending enum
Hi! There's a hypothetical (based on a similar real world problem that I am facing) example for the use of extending enums in the OP. And I don't suppose Tim was arguing for not allowing enum extendability, but rather contributing a real world example where users who were having some totally legal fun hacking around with internal PHP code would break said internal PHP code. Which may or may not be a big deal from my uneducated point of view, just saying :) And all could be prevented just by adding a `final` statement to the definition of `IntervalBoundary`. On Wed, 29 Mar 2023 at 13:42, Christian Schneider wrote: > > Am 29.03.2023 um 11:55 schrieb Tim Düsterhus : > > On 3/29/23 11:42, Sebastian Bergmann wrote: > >> Am 29.03.2023 um 11:31 schrieb Rokas Šleinius: > >>> I wouldn't say removing the final attribute from enums actually "breaks" > >>> any functionality. > >> I am with Marco on this: removing the "finality" from enum would be a > >> major backward compatiblity break as it breaks a fundamental assumption > >> about enums. > > > > And to give a specific example from PHP 8.3: As part of the Randomizer > > additions RFC (https://wiki.php.net/rfc/randomizer_additions), PHP 8.3 got > > its first "natively included" enum (\Random\IntervalBoundary). This enum > > works together with the new Randomizer::getFloat() method to specify if the > > given min and max value may be returned or not. > > > > The Randomizer::getFloat() method internally includes switch statement that > > chooses the implementation based on the IntervalBoundary value given. > > > > If a user would be able to extend the IntervalBoundary enum, the method > > would not be able to make sense of it. > > > > The IntervalBoundary enum completely enumerates all four possible > > combinations for the two possible states for each of the two boundaries > > (2*2 = 4). By definition there is no other valid value. > > I do understand the reason behind making Enums final and your example > illustrates the point very well. > > I still have a question I already asked in a different context: If there ever > is the need to add a case to an Enum, was there any thought put into making > this possible? Or is this categorically ruled out when using Enums? > > Let’s look at a very, very hypothetical example: Imagine a Randomizer is > much, much slower for certain boundaries and it is decided that some programs > do not care about Closed/Open but instead care more about speed. So they > would want to use something like IntervalBoundary::Fastest. > > As far as I understand such an addition would *never* be possible, right? > This means people defining Enums have to be very very certain that no one > will ever want another value, right? > > Regards, > - Chris > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php > -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
Am 29.03.2023 um 11:55 schrieb Tim Düsterhus : > On 3/29/23 11:42, Sebastian Bergmann wrote: >> Am 29.03.2023 um 11:31 schrieb Rokas Šleinius: >>> I wouldn't say removing the final attribute from enums actually "breaks" >>> any functionality. >> I am with Marco on this: removing the "finality" from enum would be a >> major backward compatiblity break as it breaks a fundamental assumption >> about enums. > > And to give a specific example from PHP 8.3: As part of the Randomizer > additions RFC (https://wiki.php.net/rfc/randomizer_additions), PHP 8.3 got > its first "natively included" enum (\Random\IntervalBoundary). This enum > works together with the new Randomizer::getFloat() method to specify if the > given min and max value may be returned or not. > > The Randomizer::getFloat() method internally includes switch statement that > chooses the implementation based on the IntervalBoundary value given. > > If a user would be able to extend the IntervalBoundary enum, the method would > not be able to make sense of it. > > The IntervalBoundary enum completely enumerates all four possible > combinations for the two possible states for each of the two boundaries (2*2 > = 4). By definition there is no other valid value. I do understand the reason behind making Enums final and your example illustrates the point very well. I still have a question I already asked in a different context: If there ever is the need to add a case to an Enum, was there any thought put into making this possible? Or is this categorically ruled out when using Enums? Let’s look at a very, very hypothetical example: Imagine a Randomizer is much, much slower for certain boundaries and it is decided that some programs do not care about Closed/Open but instead care more about speed. So they would want to use something like IntervalBoundary::Fastest. As far as I understand such an addition would *never* be possible, right? This means people defining Enums have to be very very certain that no one will ever want another value, right? Regards, - Chris -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
Hi On 3/29/23 11:42, Sebastian Bergmann wrote: Am 29.03.2023 um 11:31 schrieb Rokas Šleinius: I wouldn't say removing the final attribute from enums actually "breaks" any functionality. I am with Marco on this: removing the "finality" from enum would be a major backward compatiblity break as it breaks a fundamental assumption about enums. And to give a specific example from PHP 8.3: As part of the Randomizer additions RFC (https://wiki.php.net/rfc/randomizer_additions), PHP 8.3 got its first "natively included" enum (\Random\IntervalBoundary). This enum works together with the new Randomizer::getFloat() method to specify if the given min and max value may be returned or not. The Randomizer::getFloat() method internally includes switch statement that chooses the implementation based on the IntervalBoundary value given. If a user would be able to extend the IntervalBoundary enum, the method would not be able to make sense of it. The IntervalBoundary enum completely enumerates all four possible combinations for the two possible states for each of the two boundaries (2*2 = 4). By definition there is no other valid value. Best regards Tim Düsterhus -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
Am 29.03.2023 um 11:31 schrieb Rokas Šleinius: I wouldn't say removing the final attribute from enums actually "breaks" any functionality. I am with Marco on this: removing the "finality" from enum would be a major backward compatiblity break as it breaks a fundamental assumption about enums. -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
> BC break Hm, do you really think so? I wouldn't say removing the final attribute from enums actually "breaks" any functionality. It WOULD allow some potentially illicit hacking to the end users of our code, that's true. I'd say a minor possibility of impact. And anyway, isn't that what got you to fall in love with PHP in the first place? Some good ol' illicit hacking of some dude's library :) On Wed, 29 Mar 2023 at 12:22, Marco Pivetta wrote: > > Hey Rokas, > > The idea of declaring an enum `abstract` is potentially worth exploring (I'm > not convinced, but go for it), but beware that removing the implicit `final` > from ENUMs is a BC break, since it breaks the assumption that an ENUM is a > sealed type. > > If you design a proposal now, you need to consider that `final` as the > implicit default is an existing contract that really enables ENUM usage. > > Marco Pivetta > > https://mastodon.social/@ocramius > > https://ocramius.github.io/ > > > On Wed, 29 Mar 2023 at 11:05, Rokas Šleinius wrote: >> >> > to restrict options within a certain range. >> Okay that's an aspect of enums that I never gave much thought to, but >> you're completely right. >> >> However to be explicit about that aspect of functionality one would >> then have to also be >> allowed to define final enums. >> >> That way you still have both options - to restrict the option set, but >> to also allow adding >> custom ones while benefiting from other enum advantages. >> >> Thus, rfc idea seems to be: >> * enums should be able to extend other enums; >> * abstract enums; >> * enums allowed to implement interfaces; >> * final enums (NEW!) >> >> >> >> > BackedEnum >> That's a fascinating approach but to a different problem than illustrated in >> OP. >> >> Would you mind sharing (in private preferably to not distract from the >> discussion) the >> gist of the problem this solves? I'm really curious, as I've yet to >> use intersect types :)! >> >> On Wed, 29 Mar 2023 at 11:47, Robert Landers >> wrote: >> > >> > On Wed, Mar 29, 2023 at 10:31 AM Rokas Šleinius wrote: >> > > >> > > Enums were a very useful addition to PHP, however one aspect of them is >> > > neither >> > > explicitly documented - or seemingly even talked about. >> > > >> > > Enums were implemented as final so they cannot be extended nor can extend >> > > anything else. >> > > >> > > From a user perspective it's surprising - and actually limiting. >> > > >> > > USAGE EXAMPLE: >> > > I am making an error management system: each error presented to the user >> > > must have a unique code visible. >> > > >> > > ```php >> > > class SystemError >> > > { >> > > public function __construct( >> > > private string $errorText, >> > > private ErrorCode $code >> > > ) { >> > > } >> > > >> > > public function __toString(): >> > > { >> > > return $this->errorText . ' ' . $this->code->toString(); >> > > } >> > > } >> > > // ... >> > > >> > > enum ErrorCode >> > > { >> > > case Code_1; >> > > case Code_2; >> > > >> > > public function toString(): string >> > > { >> > > return 'Error code:' . substr($this->name, strlen('Code_')); >> > > } >> > > } >> > > ``` >> > > >> > > Now I want to modify it to support different modules with different >> > > namespaces for >> > > errors, e.g. an ApiError, simple enough: >> > > >> > > ```php >> > > enum BaseErrorCode >> > > { >> > > // ... >> > > } >> > > >> > > enum ErrorCode extends BaseErrorCode >> > > { >> > > case Code_1; >> > > case Code_2; >> > > >> > > // ... >> > > } >> > > >> > > enum ApiErrorCode extends BaseErrorCode { >> > > // ... >> > > function toString(): string >> > > { >> > > return 'Error code:API-' . substr($this->name, strlen('Code_')); >> > > } >> > > } >> > > ``` >> > > >> > > This results in a syntax error. >> > > >> > > PROPOSAL: >> > > >> > > Enums should be able to extend other enums. >> > > >> > > For a complete wishlist, add: >> > > * abstract enums; >> > > * enums allowed to implement interfaces; >> > > >> > > However since I have no experience in PHP source code, I can only >> > > provide the test suite for a possible PR this might have :( >> > > >> > > Do you think this is likely to get implemented? >> > > >> > > -- >> > > PHP Internals - PHP Runtime Development Mailing List >> > > To unsubscribe, visit: https://www.php.net/unsub.php >> > > >> > >> > Hey Rokas, >> > >> > My approach has been to use an intersection type like: >> > BackedEnum >> > >> > This works for me but it'd be interesting if something like that >> > wouldn't work for you. >> >> -- >> PHP Internals - PHP Runtime Development Mailing List >> To unsubscribe, visit: https://www.php.net/unsub.php >> -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
Hey Rokas, The idea of declaring an enum `abstract` is potentially worth exploring (I'm not convinced, but go for it), but beware that removing the implicit `final` from ENUMs is a BC break, since it breaks the assumption that an ENUM is a sealed type. If you design a proposal now, you need to consider that `final` as the implicit default is an existing contract that really enables ENUM usage. Marco Pivetta https://mastodon.social/@ocramius https://ocramius.github.io/ On Wed, 29 Mar 2023 at 11:05, Rokas Šleinius wrote: > > to restrict options within a certain range. > Okay that's an aspect of enums that I never gave much thought to, but > you're completely right. > > However to be explicit about that aspect of functionality one would > then have to also be > allowed to define final enums. > > That way you still have both options - to restrict the option set, but > to also allow adding > custom ones while benefiting from other enum advantages. > > Thus, rfc idea seems to be: > * enums should be able to extend other enums; > * abstract enums; > * enums allowed to implement interfaces; > * final enums (NEW!) > > > > > BackedEnum > That's a fascinating approach but to a different problem than illustrated > in OP. > > Would you mind sharing (in private preferably to not distract from the > discussion) the > gist of the problem this solves? I'm really curious, as I've yet to > use intersect types :)! > > On Wed, 29 Mar 2023 at 11:47, Robert Landers > wrote: > > > > On Wed, Mar 29, 2023 at 10:31 AM Rokas Šleinius > wrote: > > > > > > Enums were a very useful addition to PHP, however one aspect of them > is neither > > > explicitly documented - or seemingly even talked about. > > > > > > Enums were implemented as final so they cannot be extended nor can > extend > > > anything else. > > > > > > From a user perspective it's surprising - and actually limiting. > > > > > > USAGE EXAMPLE: > > > I am making an error management system: each error presented to the > user > > > must have a unique code visible. > > > > > > ```php > > > class SystemError > > > { > > > public function __construct( > > > private string $errorText, > > > private ErrorCode $code > > > ) { > > > } > > > > > > public function __toString(): > > > { > > > return $this->errorText . ' ' . $this->code->toString(); > > > } > > > } > > > // ... > > > > > > enum ErrorCode > > > { > > > case Code_1; > > > case Code_2; > > > > > > public function toString(): string > > > { > > > return 'Error code:' . substr($this->name, strlen('Code_')); > > > } > > > } > > > ``` > > > > > > Now I want to modify it to support different modules with different > > > namespaces for > > > errors, e.g. an ApiError, simple enough: > > > > > > ```php > > > enum BaseErrorCode > > > { > > > // ... > > > } > > > > > > enum ErrorCode extends BaseErrorCode > > > { > > > case Code_1; > > > case Code_2; > > > > > > // ... > > > } > > > > > > enum ApiErrorCode extends BaseErrorCode { > > > // ... > > > function toString(): string > > > { > > > return 'Error code:API-' . substr($this->name, > strlen('Code_')); > > > } > > > } > > > ``` > > > > > > This results in a syntax error. > > > > > > PROPOSAL: > > > > > > Enums should be able to extend other enums. > > > > > > For a complete wishlist, add: > > > * abstract enums; > > > * enums allowed to implement interfaces; > > > > > > However since I have no experience in PHP source code, I can only > > > provide the test suite for a possible PR this might have :( > > > > > > Do you think this is likely to get implemented? > > > > > > -- > > > PHP Internals - PHP Runtime Development Mailing List > > > To unsubscribe, visit: https://www.php.net/unsub.php > > > > > > > Hey Rokas, > > > > My approach has been to use an intersection type like: > > BackedEnum > > > > This works for me but it'd be interesting if something like that > > wouldn't work for you. > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php > >
Re: [PHP-DEV] [IDEA] allow extending enum
> to restrict options within a certain range. Okay that's an aspect of enums that I never gave much thought to, but you're completely right. However to be explicit about that aspect of functionality one would then have to also be allowed to define final enums. That way you still have both options - to restrict the option set, but to also allow adding custom ones while benefiting from other enum advantages. Thus, rfc idea seems to be: * enums should be able to extend other enums; * abstract enums; * enums allowed to implement interfaces; * final enums (NEW!) > BackedEnum That's a fascinating approach but to a different problem than illustrated in OP. Would you mind sharing (in private preferably to not distract from the discussion) the gist of the problem this solves? I'm really curious, as I've yet to use intersect types :)! On Wed, 29 Mar 2023 at 11:47, Robert Landers wrote: > > On Wed, Mar 29, 2023 at 10:31 AM Rokas Šleinius wrote: > > > > Enums were a very useful addition to PHP, however one aspect of them is > > neither > > explicitly documented - or seemingly even talked about. > > > > Enums were implemented as final so they cannot be extended nor can extend > > anything else. > > > > From a user perspective it's surprising - and actually limiting. > > > > USAGE EXAMPLE: > > I am making an error management system: each error presented to the user > > must have a unique code visible. > > > > ```php > > class SystemError > > { > > public function __construct( > > private string $errorText, > > private ErrorCode $code > > ) { > > } > > > > public function __toString(): > > { > > return $this->errorText . ' ' . $this->code->toString(); > > } > > } > > // ... > > > > enum ErrorCode > > { > > case Code_1; > > case Code_2; > > > > public function toString(): string > > { > > return 'Error code:' . substr($this->name, strlen('Code_')); > > } > > } > > ``` > > > > Now I want to modify it to support different modules with different > > namespaces for > > errors, e.g. an ApiError, simple enough: > > > > ```php > > enum BaseErrorCode > > { > > // ... > > } > > > > enum ErrorCode extends BaseErrorCode > > { > > case Code_1; > > case Code_2; > > > > // ... > > } > > > > enum ApiErrorCode extends BaseErrorCode { > > // ... > > function toString(): string > > { > > return 'Error code:API-' . substr($this->name, strlen('Code_')); > > } > > } > > ``` > > > > This results in a syntax error. > > > > PROPOSAL: > > > > Enums should be able to extend other enums. > > > > For a complete wishlist, add: > > * abstract enums; > > * enums allowed to implement interfaces; > > > > However since I have no experience in PHP source code, I can only > > provide the test suite for a possible PR this might have :( > > > > Do you think this is likely to get implemented? > > > > -- > > PHP Internals - PHP Runtime Development Mailing List > > To unsubscribe, visit: https://www.php.net/unsub.php > > > > Hey Rokas, > > My approach has been to use an intersection type like: > BackedEnum > > This works for me but it'd be interesting if something like that > wouldn't work for you. -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
On Wed, Mar 29, 2023 at 10:31 AM Rokas Šleinius wrote: > > Enums were a very useful addition to PHP, however one aspect of them is > neither > explicitly documented - or seemingly even talked about. > > Enums were implemented as final so they cannot be extended nor can extend > anything else. > > From a user perspective it's surprising - and actually limiting. > > USAGE EXAMPLE: > I am making an error management system: each error presented to the user > must have a unique code visible. > > ```php > class SystemError > { > public function __construct( > private string $errorText, > private ErrorCode $code > ) { > } > > public function __toString(): > { > return $this->errorText . ' ' . $this->code->toString(); > } > } > // ... > > enum ErrorCode > { > case Code_1; > case Code_2; > > public function toString(): string > { > return 'Error code:' . substr($this->name, strlen('Code_')); > } > } > ``` > > Now I want to modify it to support different modules with different > namespaces for > errors, e.g. an ApiError, simple enough: > > ```php > enum BaseErrorCode > { > // ... > } > > enum ErrorCode extends BaseErrorCode > { > case Code_1; > case Code_2; > > // ... > } > > enum ApiErrorCode extends BaseErrorCode { > // ... > function toString(): string > { > return 'Error code:API-' . substr($this->name, strlen('Code_')); > } > } > ``` > > This results in a syntax error. > > PROPOSAL: > > Enums should be able to extend other enums. > > For a complete wishlist, add: > * abstract enums; > * enums allowed to implement interfaces; > > However since I have no experience in PHP source code, I can only > provide the test suite for a possible PR this might have :( > > Do you think this is likely to get implemented? > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php > Hey Rokas, My approach has been to use an intersection type like: BackedEnum This works for me but it'd be interesting if something like that wouldn't work for you. -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] [IDEA] allow extending enum
The point of an ENUM is precisely to restrict options within a certain range. Assume you declared `ErrorCode` with `SOMETHING_BROKE` and `PEBKAC` as possible enumeration values. If you can (somewhere, anywhere in the code) check `instanceof ErrorCode`, then you know it can only be `SOMETHING_BROKE` and `PEBKAC`, and you can write logic around it accordingly. This restriction/assumption is what makes ENUMs powerful: expanding pre-existing enums would eliminate this very core design principle, since you may now no longer have an `ErrorCode`, but some child class designed somewhere that may not even be under your control. Greets, Marco Pivetta https://mastodon.social/@ocramius https://ocramius.github.io/ On Wed, 29 Mar 2023 at 10:31, Rokas Šleinius wrote: > Enums were a very useful addition to PHP, however one aspect of them is > neither > explicitly documented - or seemingly even talked about. > > Enums were implemented as final so they cannot be extended nor can extend > anything else. > > From a user perspective it's surprising - and actually limiting. > > USAGE EXAMPLE: > I am making an error management system: each error presented to the user > must have a unique code visible. > > ```php > class SystemError > { > public function __construct( > private string $errorText, > private ErrorCode $code > ) { > } > > public function __toString(): > { > return $this->errorText . ' ' . $this->code->toString(); > } > } > // ... > > enum ErrorCode > { > case Code_1; > case Code_2; > > public function toString(): string > { > return 'Error code:' . substr($this->name, strlen('Code_')); > } > } > ``` > > Now I want to modify it to support different modules with different > namespaces for > errors, e.g. an ApiError, simple enough: > > ```php > enum BaseErrorCode > { > // ... > } > > enum ErrorCode extends BaseErrorCode > { > case Code_1; > case Code_2; > > // ... > } > > enum ApiErrorCode extends BaseErrorCode { > // ... > function toString(): string > { > return 'Error code:API-' . substr($this->name, strlen('Code_')); > } > } > ``` > > This results in a syntax error. > > PROPOSAL: > > Enums should be able to extend other enums. > > For a complete wishlist, add: > * abstract enums; > * enums allowed to implement interfaces; > > However since I have no experience in PHP source code, I can only > provide the test suite for a possible PR this might have :( > > Do you think this is likely to get implemented? > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php > >
[PHP-DEV] [IDEA] allow extending enum
Enums were a very useful addition to PHP, however one aspect of them is neither explicitly documented - or seemingly even talked about. Enums were implemented as final so they cannot be extended nor can extend anything else. >From a user perspective it's surprising - and actually limiting. USAGE EXAMPLE: I am making an error management system: each error presented to the user must have a unique code visible. ```php class SystemError { public function __construct( private string $errorText, private ErrorCode $code ) { } public function __toString(): { return $this->errorText . ' ' . $this->code->toString(); } } // ... enum ErrorCode { case Code_1; case Code_2; public function toString(): string { return 'Error code:' . substr($this->name, strlen('Code_')); } } ``` Now I want to modify it to support different modules with different namespaces for errors, e.g. an ApiError, simple enough: ```php enum BaseErrorCode { // ... } enum ErrorCode extends BaseErrorCode { case Code_1; case Code_2; // ... } enum ApiErrorCode extends BaseErrorCode { // ... function toString(): string { return 'Error code:API-' . substr($this->name, strlen('Code_')); } } ``` This results in a syntax error. PROPOSAL: Enums should be able to extend other enums. For a complete wishlist, add: * abstract enums; * enums allowed to implement interfaces; However since I have no experience in PHP source code, I can only provide the test suite for a possible PR this might have :( Do you think this is likely to get implemented? -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php