Re: [PHP-DEV] [IDEA] allow extending enum

2023-03-30 Thread Ilija Tovilo

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

2023-03-30 Thread Dan Ackroyd
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

2023-03-30 Thread Tim Düsterhus

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

2023-03-30 Thread Rokas Šleinius
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

2023-03-29 Thread Larry Garfield
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

2023-03-29 Thread Mark Baker


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

2023-03-29 Thread Tim Düsterhus

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

2023-03-29 Thread Tim Düsterhus

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

2023-03-29 Thread Larry Garfield
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

2023-03-29 Thread Rowan Tommins
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

2023-03-29 Thread Mark Baker

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

2023-03-29 Thread Davey Shafik


> 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

2023-03-29 Thread Rokas Šleinius
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

2023-03-29 Thread Rowan Tommins
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

2023-03-29 Thread Rokas Šleinius
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

2023-03-29 Thread Rowan Tommins
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

2023-03-29 Thread G. P. B.
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

2023-03-29 Thread Rokas Šleinius
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

2023-03-29 Thread Christian Schneider
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

2023-03-29 Thread Tim Düsterhus

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

2023-03-29 Thread Sebastian Bergmann

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

2023-03-29 Thread Rokas Šleinius
> 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

2023-03-29 Thread Marco Pivetta
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

2023-03-29 Thread Rokas Šleinius
> 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

2023-03-29 Thread Robert Landers
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

2023-03-29 Thread Marco Pivetta
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

2023-03-29 Thread Rokas Šleinius
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