Fwd: Re[2]: PHP 8.1 enum const expressions problem

php.internals

Кирилл Несмеянов

5 years ago

Marc

5 years ago
On 30.07.21 02:01, Кирилл Несмеянов wrote:
> Yes, I saw this short description in your RFC ( https://wiki.php.net/rfc/enumerations ) about «enum sets». However, I do not quite understand why we can not now add a cast to scalars (string and int) and math expressions, which would solve this problem? This behavior has already been implemented for GMP objects. > > $mask = gmp_init(0b0001) | gmp_init(0b0010); // object(GMP) { num = 3; } > echo $mask; // 3 > > $mask = $mask + 1; // object(GMP) { num = 4; } > $mask instanceof \GMP; // true > > I mean, for such cases, we can create a new "virtual enum case" containing a new value instead special «EnumSetCase». > > enum Some: int > { >     case A = 0b0001; >     case B = 0b0010; > } > > var_dump(Some::A | Some::B); // enum(Some::@anonymous) { value = 3; } > > I don’t think that it is necessary to consider the «enum sets» as a separate case, cause addition is also a fairly popular case: > > case LAST = self::B + 1; > > Like any other mathematical operations.
I think you missing something Consider this example: enum Some: int {     case A = 0b0001;     case B = 0b0010; case C = 0b0011; } var_dump(Some::B | Some::C) This should result in a set of B|C but with your logic it's the same as just C. and it also needs to work with strings: enum Some: string {     case A = 'a';     case B = 'b'; case C = 'c'; } This is where enum sets comes into play. Without having PHP internals C knowledge I think it should be possible to introduce an EnumSet which internally handles a bit array where each bit is pointing to the position (ordinal) of an enum case but I don't think the ordinal position is guarantied to be stable over processes/versions so this would not directly by serializable nor do we have generics to define an enum set of a specific type (ala EnumSet<Some>). Marc

Кирилл Несмеянов

5 years ago

Rowan Collins

5 years ago
On 30/07/2021 13:20, Kirill Nesmeyanov wrote:
> But in addition to bit-masks, there are also at least cases of addition and subtraction (the most popular). Can't we adapt all math expressions existing in nature to specific cases, thereby building our own pseudo-AST? > > (Some::A + Some::B) * Some::C | Some::D;
If you are performing arithmetic on a value, that value is not of an enum type, or at least not in the way the current feature defines "enum". If you write an enum for days of the week, you might give them integer values, such that "Day::MONDAY->value === 1" and "Day::SUNDAY->value === 7". However, expressions such as "Day::SATURDAY + Day::SUNDAY"  and "Day::MONDAY * Day::TUESDAY" are clearly meaningless. At a stretch, a language with operator overloading might define "Day::MONDAY + 1" to return "Day::TUESDAY", and "Day::MONDAY + 10" to either error or wrap around to "Day::THURSDAY"; but PHP has covered that use case by allowing methods, e.g. "Day::MONDAY->advanceBy(10)", which is more flexible and arguably more expressive. On the other hand, the expression "$weekend = Day::SATURDAY | Day::SUNDAY" seems reasonable; but we don't need the result to be an integer - it would have no meaning on its own - we just need to be able to ask things like "is $today in $weekend?" Returning some kind of EnumSet object means we retain the type information, and can keep our values of 1 to 7 (or have no values at all) rather than having to use powers of 2. Regards,
-- Rowan Tommins [IMSoP]

Кирилл Несмеянов

5 years ago

Rowan Collins

5 years ago
On 30/07/2021 18:24, Kirill Nesmeyanov wrote:
> enum ErrorGroup: int > { >     case NOTICE = 0; >     case WARNING = 10; >     case ERROR = 20; > } > > enum ErrorCode: int > { >     case UNKNOWN = 0; > >     case A = ErrorGroup::NOTICE + 1; >     case B = ErrorGroup::NOTICE + 2; >     case С = ErrorGroup::NOTICE + 3; >     case D = ErrorGroup::WARNING + 1; >     case E = ErrorGroup::ERROR + 1; > }
As I say, those are not enums in the way the current feature defines "enum". An enum, as implemented in the current 8.1 alphas, is not just a special kind of constant, even if it has an integer or string associated. Every enum is its own type (essentially a class), and every value is a distinct value of that type (essentially an instance), which compares equal only to itself. var_dump(ErrorGroup::WARNING); // enum(ErrorGroup::WARNING); var_dump(ErrorGroup::WARNING == 10); // bool(false) var_dump(get_class(ErrorGroup::WARNING)); // string(10) "ErrorGroup" There are languages where enums are just fancy names for integers, and you can write things like "if ( ErrorCode::D >= ErrorGroup::WARNING )". In my opinion, that makes enums much less useful, because it means you can also write nonsensical things like "if ( Month::JANUARY <= Day::WEDNESDAY )". If you want to pass around an integer, but have a convenient name for it, you can still use a class constant. The main value of native enums, in my opinion, is being able to write strongly typed code saying "this accepts a value of type ErrorGroup, and nothing else". Regards,
-- Rowan Tommins [IMSoP]