# [RFC] Enumerations

- Thread: https://externals.io/message/112417
- Original archive: https://news-web.php.net/php.internals/112417
- Started by Larry Garfield on 2020-12-04T23:24:32+00:00
- Messages: 54
- RFC: Enumerations (Implemented (PHP 8.1)) — https://externals.io/rfc/enumerations.md
- RFC: Algebraic data types (Meta RFC) (Inactive) — https://externals.io/rfc/adts.md
- Page 1 of 3: messages 1 to 25 (25 per page, add `?page=N` to the URL)
- Next page: https://externals.io/message/112417.md?page=2
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #112417 — Larry Garfield — 2020-12-04T23:24:32+00:00

URL: https://externals.io/message/112417#112417

Greetings, denizens of Internals!

Ilija Tovilo and I have been working for the last few months on adding support for enumerations and algebraic data types to PHP.  This is a not-small task, so we've broken it up into several stages.  The first stage, unit enumerations, are just about ready for public review and discussion.

The overarching plan (for context, NOT the thing to comment on right now) is here: https://wiki.php.net/rfc/adts

The first step, for unit enumerations, is here:

https://wiki.php.net/rfc/enumerations

There's still a few bits we're sorting out and the implementation is mostly done, but not 100% complete.  Still, it's far enough along to start a discussion on and get broader feedback on the outstanding nits.

I should note that while the design has been collaborative, credit for the implementation goes entirely to Ilija.  Blame for any typos in the RFC itself go entirely to me.

*dons flame-retardant suit*

-- 
  Larry Garfield
  larry@garfieldtech.com

---

## #112418 — Paul Crovella — 2020-12-05T01:37:37+00:00

URL: https://externals.io/message/112417#112418 · In reply to: #112417

>
> Greetings, denizens of Internals!
>
> Ilija Tovilo and I have been working for the last few months on adding support for enumerations and algebraic data types to PHP.  This is a not-small task, so we've broken it up into several stages.  The first stage, unit enumerations, are just about ready for public review and discussion.
>
> The overarching plan (for context, NOT the thing to comment on right now) is here: https://wiki.php.net/rfc/adts
>
> The first step, for unit enumerations, is here:
>
> https://wiki.php.net/rfc/enumerations
>
> There's still a few bits we're sorting out and the implementation is mostly done, but not 100% complete.  Still, it's far enough along to start a discussion on and get broader feedback on the outstanding nits.
>
> I should note that while the design has been collaborative, credit for the implementation goes entirely to Ilija.  Blame for any typos in the RFC itself go entirely to me.
>
> *dons flame-retardant suit*
>
> --
>   Larry Garfield
>   larry@garfieldtech.com

I'd like to see the specific reasons for the restrictions listed in
Comparison to objects[1]. In general if something's value is even
debatable then the default position should be to remain consistent
with the rest of the language. It should take a strong argument to
introduce any artificial limitation and it's useful to have that in
the RFC.

[1] https://wiki.php.net/rfc/enumerations#comparison_to_objects

---

## #112419 — Larry Garfield — 2020-12-05T02:59:34+00:00

URL: https://externals.io/message/112417#112419 · In reply to: #112418

Dec 4, 2020 7:37:51 PM Paul Crovella <paul.crovella@gmail.com>:

> 
> > […]
> 
> I'd like to see the specific reasons for the restrictions listed in
> Comparison to objects[1]. In general if something's value is even
> debatable then the default position should be to remain consistent
> with the rest of the language. It should take a strong argument to
> introduce any artificial limitation and it's useful to have that in
> the RFC.
> 
> [1] https://wiki.php.net/rfc/enumerations#comparison_to_objects
> 
The reasoning general comes down to one of 2 things:

* they involve state, and enum cases have no state. They may get reintroduced with tagged unions, but for now methods relating to state would just be confusing.
* we couldn't figure out what possible use they'd have (like static methods on cases, which without data are exactly the same as normal methods.)

--Larry Garfield

---

## #112420 — Pierre R. — 2020-12-05T09:22:34+00:00

URL: https://externals.io/message/112417#112420 · In reply to: #112417

Le 05/12/2020 à 00:24, Larry Garfield a écrit :
> Greetings, denizens of Internals!
>
> Ilija Tovilo and I have been working for the last few months on adding support for enumerations and algebraic data types to PHP.  This is a not-small task, so we've broken it up into several stages.  The first stage, unit enumerations, are just about ready for public review and discussion.
>
> The overarching plan (for context, NOT the thing to comment on right now) is here: https://wiki.php.net/rfc/adts
>
> The first step, for unit enumerations, is here:
>
> https://wiki.php.net/rfc/enumerations
>
> There's still a few bits we're sorting out and the implementation is mostly done, but not 100% complete.  Still, it's far enough along to start a discussion on and get broader feedback on the outstanding nits.
>
> I should note that while the design has been collaborative, credit for the implementation goes entirely to Ilija.  Blame for any typos in the RFC itself go entirely to me.
>
> *dons flame-retardant suit*
>
Hello !

I fully support all this initiative. Here is a first question, regarding 
the enum::cases() static method:

 > If the enumeration has no primitive equivalent, the array will be 
packed (indexed sequentially starting from 0). If the enumeration has a 
primitive equivalent, the keys will be the corresponding primitive for 
each enumeration. If the enumeration is of type |float|, the keys will 
be rendered as strings. (So a primitive equivalent of |1.5| will result 
in a key of |“1.5”|.)

Does this mean that an enum can't have two cases with the same primitive 
value ? I would very much being able to do so, for example, when you 
change a name and want to keep the legacy for backward compatibility.

I think that ::cases() should always return an array/iterable without 
keys, and let userland write their own code to create hashmaps with 
those when they need it. I think that having one case (non primitive 
cases) that doesn't yield string keys and the other (primitive cases) 
which holds string keys may create a confusion because behavior is 
different.

If behavior is different, this would mean you couldn't mix primitive and 
non primitive cases on the same enum, which, why not couldn't we do that?

Regards,

Pierre

---

## #112421 — Pierre R. — 2020-12-05T09:26:58+00:00

URL: https://externals.io/message/112417#112421 · In reply to: #112417

Le 05/12/2020 à 00:24, Larry Garfield a écrit :
> Greetings, denizens of Internals!
>
> Ilija Tovilo and I have been working for the last few months on adding support for enumerations and algebraic data types to PHP.  This is a not-small task, so we've broken it up into several stages.  The first stage, unit enumerations, are just about ready for public review and discussion.
>
> The overarching plan (for context, NOT the thing to comment on right now) is here: https://wiki.php.net/rfc/adts
>
> The first step, for unit enumerations, is here:
>
> https://wiki.php.net/rfc/enumerations
>
> There's still a few bits we're sorting out and the implementation is mostly done, but not 100% complete.  Still, it's far enough along to start a discussion on and get broader feedback on the outstanding nits.
>
> I should note that while the design has been collaborative, credit for the implementation goes entirely to Ilija.  Blame for any typos in the RFC itself go entirely to me.
>
> *dons flame-retardant suit*
>
Another question, about match() behavior:

 > This usage requires no modification of |match|. It is a natural 
implication of the current functionality.

May be this could be the time to have a "strict match", using enums, we 
can "statically" guess if branches are missing (of course, whenever you 
use default it valid to miss branches) - one thing I'd love is PHP to 
throw a fatal error when compiling a match expression whose missing 
branches, and not wait until runtime to fail.

Regards,

Pierre

---

## #112422 — Markus Fischer — 2020-12-05T11:14:33+00:00

URL: https://externals.io/message/112417#112422 · In reply to: #112420

Hi,


> I think that ::cases() should always return an array/iterable without 
> keys, and let userland write their own code to create hashmaps with 
> those when they need it. I think that having one case (non primitive 
> cases) that doesn't yield string keys and the other (primitive cases) 
> which holds string keys may create a confusion because behavior is 
> different.
> 
> If behavior is different, this would mean you couldn't mix primitive and 
> non primitive cases on the same enum, which, why not couldn't we do that?

I've no strong feelings and I think your idea makes sense.

But I would argue, of course without having hard facts, that the 
_majority_ will have the use-case for a 1:1, aka array with key => 
value, mapping.

Thus I would argue that having (considering your proposal being added) 
still such functionality would be useful for a very wide-audience 
without everyone having to reinvent a helper method to use the iterable.

thanks for considering,
- Markus

---

## #112423 — Pierre R. — 2020-12-05T12:39:28+00:00

URL: https://externals.io/message/112417#112423 · In reply to: #112422

Le 05/12/2020 à 12:14, Markus Fischer a écrit :
> Hi,
>
> 
> > […]
>
> I've no strong feelings and I think your idea makes sense.
>
> But I would argue, of course without having hard facts, that the 
> _majority_ will have the use-case for a 1:1, aka array with key => 
> value, mapping.
>
> Thus I would argue that having (considering your proposal being added) 
> still such functionality would be useful for a very wide-audience 
> without everyone having to reinvent a helper method to use the iterable.
>
I agree with that last statement about wide-audience usefulness, 
nevertheless my opinion is that it's not about being useful to the most 
but bring consistency at the language level. If for consistency I have 
to write an `array_map()` call whenever I need a choices list, I'm good 
with that. Moreover, bringing enum is a thousands times more useful than 
having a sugar candy method for building hashmaps !

There are many use cases you'd want an hashmap out of those enums, but 
each use case will probably differ in subtle ways. I'd more comfortable 
with a coherent behavior that everyone will understand easily, with no 
behavior subtleties, and each user implement its need on this side.

Why not, in that case, just add another method than ::cases(), such as, 
I don't know, ::to[Hash]Map() for example ?

Regards,

Pierre

---

## #112424 — Marc Bennewitz — 2020-12-05T14:21:19+00:00

URL: https://externals.io/message/112417#112424 · In reply to: #112417

Am 05.12.20, 00:25 schrieb "Larry Garfield" <larry@garfieldtech.com>:

    Greetings, denizens of Internals!

    Ilija Tovilo and I have been working for the last few months on adding support for enumerations and algebraic data types to PHP.  This is a not-small task, so we've broken it up into several stages.  The first stage, unit enumerations, are just about ready for public review and discussion.

    The overarching plan (for context, NOT the thing to comment on right now) is here: https://wiki.php.net/rfc/adts

    The first step, for unit enumerations, is here:

    https://wiki.php.net/rfc/enumerations

    There's still a few bits we're sorting out and the implementation is mostly done, but not 100% complete.  Still, it's far enough along to start a discussion on and get broader feedback on the outstanding nits.

    I should note that while the design has been collaborative, credit for the implementation goes entirely to Ilija.  Blame for any typos in the RFC itself go entirely to me.

    *dons flame-retardant suit*

Hi Larry,

thanks for your great initiative and hard work in this!
I'm the author of an emulated enumeration lib [1] and really looking forward seeing native enumeration support in PHP.

Here are some questions about your RFC:

* How can you access a defined case value? 
  Something like `Suit::Spades->value()`

* Are the cases case-insensitive or case-sensitive?
  `Suit::Spades` vs. `Suit::SPADES`

* Are cases serializable?
  `Suit::Spades === unserialize(serialize(Suit::Spades)) // true`

* Do cases have a stable ordinal number / position and is that accessible?
  This would be very interesting on implementing an optimized EnumSet using bit-array / bit-set internally

* I often use metadata in enumerations and so I would be very interested to allow constants.
  I do understand that they share the same naming table and these needs to be unique but disabling constants altogether would limit the use-cases in my opinion.

* Is it possible to detect / type-hint if something is any enum?
  ` Suit::Spades instanceof Enum`

  This basically means that all enumerations would to based on a general enum.
  This would be very helpful on providing functionalities especially for enumerations thinking about a doctrine enumeration type or again an EnumSet / EnumMap.

Thanks Marc

[1] https://github.com/marc-mabe/php-enum

    -- 
      Larry Garfield
      larry@garfieldtech.com

    -- 
    
    To unsubscribe, visit: https://www.php.net/unsub.php

---

## #112425 — Larry Garfield — 2020-12-05T14:42:44+00:00

URL: https://externals.io/message/112417#112425 · In reply to: #112421

> Le 05/12/2020 à 00:24, Larry Garfield a écrit :
> > […]
> Another question, about match() behavior:
> 
> > […]
> implication of the current functionality.
> 
> May be this could be the time to have a "strict match", using enums, we 
> can "statically" guess if branches are missing (of course, whenever you 
> use default it valid to miss branches) - one thing I'd love is PHP to 
> throw a fatal error when compiling a match expression whose missing 
> branches, and not wait until runtime to fail.

I think that would make a great follow-up, but it's out of scope for now.  Having match statically know what the available types are when the variable type isn't yet known (because it's in another file) is... I don't know how to do that.  That's a broad problem across PHP, frankly.  If we can figure out a way to do so, I'd support adding it in the future.  (I can't speak for Ilija, but I suspect he would be on board as well.)

--Larry Garfield

---

## #112426 — Pierre R. — 2020-12-05T15:05:53+00:00

URL: https://externals.io/message/112417#112426 · In reply to: #112425

Le 05/12/2020 à 15:42, Larry Garfield a écrit :
> 
> > […]
> I think that would make a great follow-up, but it's out of scope for now.  Having match statically know what the available types are when the variable type isn't yet known (because it's in another file) is... I don't know how to do that.  That's a broad problem across PHP, frankly.  If we can figure out a way to do so, I'd support adding it in the future.  (I can't speak for Ilija, but I suspect he would be on board as well.)
>
> --Larry Garfield

Yes, I guess that's a broad problem that could only be fixed with 
package handling at the language level, and autoloading done by the 
compiler and not userland (simply put, that can only be fixed by killing 
autoloading once for all). But that's a much more complex and 
bikeshedding topic. Let's consider that your RFC is good as it is (and 
it is honestly, I love it).

Regards,

Pierre

---

## #112427 — Larry Garfield — 2020-12-05T15:08:02+00:00

URL: https://externals.io/message/112417#112427 · In reply to: #112424

>     *dons flame-retardant suit*
> 
> Hi Larry,
> 
> thanks for your great initiative and hard work in this!
> I'm the author of an emulated enumeration lib [1] and really looking 
> forward seeing native enumeration support in PHP.
> 
> Here are some questions about your RFC:
> 
> * How can you access a defined case value? 
>   Something like `Suit::Spades->value()`

At the moment it's only accessible via the ::cases() method.  It may be appropriate to yoink the ->value() or ->value name to access it another way.  We're debating that right now.

The primitive equivalent case is tricky, because other languages have about 14 different ways to think about it, all of them in the end incompatible. :-)  We know we don't want to just have "fancy constants," but for those cases you do want/need a primitive instead (mainly writing to a DB or screen) it needs to be easy enough to get that.  There's a number of competing priorities we're trying to balance here to make the DX as clean as possible.

To also respond to Pierre here, at present the primitives must be unique.  We want to minimize the boilerplate that people have to write on every enum for common cases, and the cases() method is part of that.  It won't capture everything, obviously, but if the common cases can be made trivial and the rare cases possible, that's a win.

> * Are the cases case-insensitive or case-sensitive?
>   `Suit::Spades` vs. `Suit::SPADES`

They end up as class names internally, so I believe that means they'd be case insensitive.

> * Are cases serializable?
>   `Suit::Spades === unserialize(serialize(Suit::Spades)) // true`

Right now they'd do the same as objects, so they'd serialize as an object.  Unserializing like that, though... hm, that would probably NOT still be === due to the way PHP handles objects.  That's probably undesireable, but I'm not sure at the moment the best way around that.  I'll have to discuss with Iliya.

> * Do cases have a stable ordinal number / position and is that 
> accessible?
>   This would be very interesting on implementing an optimized EnumSet 
> using bit-array / bit-set internally

The order returned from cases() is stable as lexical order, but there's no ordinal number to access.  If you want a bit set, you could do:

enum Permissions: int {
  case Read = 0x1,
  case Write = 0x10,
  case Exec = 0x100,
}

Right now there's no support for case ReadWrite = self::Read | self::Write.  I don't know if that would be easy to add in the future, but I'd be OK with it if so.  Mainly this runs into the same tricky questions around primitive equivalent handling (and what we can coax the lexer to do).

> * I often use metadata in enumerations and so I would be very 
> interested to allow constants.
>   I do understand that they share the same naming table and these needs 
> to be unique but disabling constants altogether would limit the 
> use-cases in my opinion.

That's what methods are for, or potentially __get.  Allowing even 

> * Is it possible to detect / type-hint if something is any enum?
>   ` Suit::Spades instanceof Enum`
> 
>   This basically means that all enumerations would to based on a 
> general enum.
>   This would be very helpful on providing functionalities especially 
> for enumerations thinking about a doctrine enumeration type or again an 
> EnumSet / EnumMap.

Not at the moment.  We're discussing the implications of adding that.

What exactly are EnumSet and EnumMap, in your mind?

--Larry Garfield

---

## #112428 — Rowan Tommins — 2020-12-05T15:21:28+00:00

URL: https://externals.io/message/112417#112428 · In reply to: #112424

(Apologies if this posts twice, I may have accidentally hit send before 
I'd finished writing it)



> * Do cases have a stable ordinal number / position and is that accessible?
>    This would be very interesting on implementing an optimized EnumSet using bit-array / bit-set internally

If you want to associate a stable value with each case, you can use a 
"primitive-equivalent enum" and assign each case an integer manually. As 
the RFC says:

 > There are no auto-generated primitive equivalents (e.g., sequential 
integers).


I think that's a good design decision, because the language itself can't 
actually guarantee a stable ordinal number if the enum's author makes 
changes; e.g. given:

enum Colour { case RED; case GREEN; case BLUE; }

somebody might decide the cases should be in alphabetical order:

enum Colour { case BLUE; case GREEN; case RED; }

It seems unnecessarily confusing to have this be a breaking change 
because code somewhere relied on the ordering. It's much clearer if the 
author has to declare an intentional value, and can then maintain it 
when making cosmetic changes to their own code:

enum Colour { case BLUE=3; case GREEN=2; case RED=1; }


> * I often use metadata in enumerations and so I would be very 
> interested to allow constants.


Could you give an example what you mean? Metadata on individual cases is 
supported by methods, which map more cleanly to things like interfaces, 
and the notion of each case as a singleton object, not a static class.


That said, I have a related question: can enum cases be used as the 
*value* of constants? e.g.:

class OldMaid {
     public const SUIT = Suit::Spades;
     public const VALUE = CardValue::Queen;
}


If so, this leads to an interesting use case for constants on the enum 
itself (not the cases) to define aliases:

enum Suit {
   case Hearts;
   case Diamonds;
   case Clubs;
   case Spades;
   const Tiles = self::Diamonds;
   const Clovers = self::Clubs;
   const Pikes = self::Spades;
}

// The constants and cases can be used interchangeably, since constants 
and cases are de-referenced with the same syntax
assert(Suit::Spades === Suit::Pikes);


Regards,

-- 
Rowan Tommins (né Collins)
[IMSoP]

---

## #112429 — Rowan Tommins — 2020-12-05T15:47:07+00:00

URL: https://externals.io/message/112417#112429 · In reply to: #112427

> > […]
> Right now they'd do the same as objects, so they'd serialize as an object.  Unserializing like that, though... hm, that would probably NOT still be === due to the way PHP handles objects.  That's probably undesireable, but I'm not sure at the moment the best way around that.  I'll have to discuss with Iliya.
>

I guess what it comes down to is whether / how easily a class can return 
an existing instance when asked to unserialize, rather than setting 
properties on an existing instance. That is, given the string 
"C:4:Suit:6:{Spades}" can the class definition return the appropriate 
singleton for Suits::Spades rather than a newly constructed object?

If this proves tricky to implement, it would probably be better to 
forbid serialization than using the default object format and breaking 
the singleton-ness of the case objects.


Regards,

-- 
Rowan Tommins (né Collins)
[IMSoP]

---

## #112430 — tyson andre — 2020-12-05T16:15:32+00:00

URL: https://externals.io/message/112417#112430 · In reply to: #112427

Hi Larry Garfield,

> Right now they'd do the same as objects, so they'd serialize as an object.
> Unserializing like that, though... hm, that would probably NOT still be === due to the way PHP handles objects.
> That's probably undesireable, but I'm not sure at the moment the best way around that.  I'll have to discuss with Iliya.

At a glance, it seems like it's doable in php 8.1 internals to make the unserializer return a singleton if you override the C `unserialize` callback of the class (this could introduce edge cases during unserialization failure for destruction, expected to be solvable).
This would require using the `C:` `serialize()` encoding (php uses that for classes implementing Serializable), not `o:`
(unserialize can be set to a generic C function that creates a value and puts it in `*object*`)

```
// Zend/zend.h
struct _zend_class_entry {
	/* .... serializer callbacks */
	int (*serialize)(zval *object, unsigned char **buffer, size_t *buf_len, zend_serialize_data *data);
	int (*unserialize)(zval *object, zend_class_entry *ce, const unsigned char *buf, size_t buf_len, zend_unserialize_data *data);

// ext/standard/var_unserializer.c, in object_custom()
	if (ce->unserialize == NULL) {
		zend_error(E_WARNING, "Class %s has no unserializer", ZSTR_VAL(ce->name));
		object_init_ex(rval, ce);
	} else if (ce->unserialize(rval, ce, (const unsigned char*)*p, datalen, (zend_unserialize_data *)var_hash) != SUCCESS) {
		return 0;
	}
```

(I'm one of the maintainers of https://pecl.php.net/package/igbinary , an alternative binary php serializer)

- Tyson

---

## #112431 — Marc Bennewitz — 2020-12-05T19:08:36+00:00

URL: https://externals.io/message/112417#112431 · In reply to: #112427

﻿Am 05.12.20, 16:08 schrieb "Larry Garfield" <larry@garfieldtech.com>:

    
    > 
    > * How can you access a defined case value? 
    >   Something like `Suit::Spades->value()`

    At the moment it's only accessible via the ::cases() method.  It may be appropriate to yoink the ->value() or ->value name to access it another way.  We're debating that right now.

    The primitive equivalent case is tricky, because other languages have about 14 different ways to think about it, all of them in the end incompatible. :-)  We know we don't want to just have "fancy constants," but for those cases you do want/need a primitive instead (mainly writing to a DB or screen) it needs to be easy enough to get that.  There's a number of competing priorities we're trying to balance here to make the DX as clean as possible.

For me it doesn't really matter how to get the primitive value as long as there is a simple way to do so.

As you wrote
> Passing a Primitive Case to a primitive-typed parameter or return will produce the primitive value in weak-typing mode, and produce a TypeError in strict-typing mode.
This would already break simple function calls like the following on strlen

enum Suit: string {
  case Hearts = 'H';
  ...
  public function label(): string { return 'Label of ' . $this . ' with  length ' . strlen($this); }
  // or more clear in my opinion
  public function label(): string { return 'Label of ' . $this->value . ' with  length ' . strlen($this->value); }
}

    To also respond to Pierre here, at present the primitives must be unique.  We want to minimize the boilerplate that people have to write on every enum for common cases, and the cases() method is part of that.  It won't capture everything, obviously, but if the common cases can be made trivial and the rare cases possible, that's a win.

I agree here that the defined primitives needs to be unique as well.


    > * Do cases have a stable ordinal number / position and is that 
    > accessible?
    >   This would be very interesting on implementing an optimized EnumSet 
    > using bit-array / bit-set internally

    The order returned from cases() is stable as lexical order, but there's no ordinal number to access.  If you want a bit set, you could do:

    enum Permissions: int {
      case Read = 0x1,
      case Write = 0x10,
      case Exec = 0x100,
    }

    Right now there's no support for case ReadWrite = self::Read | self::Write.  I don't know if that would be easy to add in the future, but I'd be OK with it if so.  Mainly this runs into the same tricky questions around primitive equivalent handling (and what we can coax the lexer to do).

    > * Is it possible to detect / type-hint if something is any enum?
    >   ` Suit::Spades instanceof Enum`
    > 
    >   This basically means that all enumerations would to based on a 
    > general enum.
    >   This would be very helpful on providing functionalities especially 
    > for enumerations thinking about a doctrine enumeration type or again an 
    > EnumSet / EnumMap.

    Not at the moment.  We're discussing the implications of adding that.

    What exactly are EnumSet and EnumMap, in your mind?

An EnumSet is a set (unique cases) of the same enumeration type.
An EnumMap maps cases of the same enumeration type to another value.

The interesting part here is that this can be done in a very efficient way without the need to iterate over it.
Like you have defined an enumeration of 200 countries

enum Country: string {
    case USA = 'US',
    case Germany = 'DE',
    case Australia = 'AU',
    ...
}

$set1 = Country::USA | Country::Germany; // EnumSet<Country> of [Country::USA, Country::Germany]
$set2 = Country::USA | Country:: Australia; // EnumSet<Country> of [Country::USA, Country::Australia]
$set3 = $set1 & $set2; // EnumSet<Country> of [Country::USA]
$set4 = $set1 & ~$set2; // EnumSet<Country> of [Country:: Germany]

This could of course also be done without operator overloading but it looks very clean with them __
All done internally using bit operations __


Greetings Marc


    --Larry Garfield

    -- 
    
    To unsubscribe, visit: https://www.php.net/unsub.php

---

## #112432 — Marc Bennewitz — 2020-12-05T19:21:13+00:00

URL: https://externals.io/message/112417#112432 · In reply to: #112428

﻿Am 05.12.20, 16:21 schrieb "Rowan Tommins" <rowan.collins@gmail.com>:

    (Apologies if this posts twice, I may have accidentally hit send before 
    I'd finished writing it)


    
    > * Do cases have a stable ordinal number / position and is that accessible?
    >    This would be very interesting on implementing an optimized EnumSet using bit-array / bit-set internally

    If you want to associate a stable value with each case, you can use a 
    "primitive-equivalent enum" and assign each case an integer manually. As 
    the RFC says:

     > There are no auto-generated primitive equivalents (e.g., sequential 
    integers).


    I think that's a good design decision, because the language itself can't 
    actually guarantee a stable ordinal number if the enum's author makes 
    changes; e.g. given:

    enum Colour { case RED; case GREEN; case BLUE; }

    somebody might decide the cases should be in alphabetical order:

    enum Colour { case BLUE; case GREEN; case RED; }

    It seems unnecessarily confusing to have this be a breaking change 
    because code somewhere relied on the ordering. It's much clearer if the 
    author has to declare an intentional value, and can then maintain it 
    when making cosmetic changes to their own code:

    enum Colour { case BLUE=3; case GREEN=2; case RED=1; }


Sorry for the confusion - I mean stable within the same process - not over different processes / systems.


    > * I often use metadata in enumerations and so I would be very 
    > interested to allow constants.


    Could you give an example what you mean? Metadata on individual cases is 
    supported by methods, which map more cleanly to things like interfaces, 
    and the notion of each case as a singleton object, not a static class.


    That said, I have a related question: can enum cases be used as the 
    *value* of constants? e.g.:

    class OldMaid {
         public const SUIT = Suit::Spades;
         public const VALUE = CardValue::Queen;
    }


    If so, this leads to an interesting use case for constants on the enum 
    itself (not the cases) to define aliases:

    enum Suit {
       case Hearts;
       case Diamonds;
       case Clubs;
       case Spades;
       const Tiles = self::Diamonds;
       const Clovers = self::Clubs;
       const Pikes = self::Spades;
    }

    // The constants and cases can be used interchangeably, since constants 
    and cases are de-referenced with the same syntax
    assert(Suit::Spades === Suit::Pikes);


I mean on mapping something to something else defined as a single assoc array constant.
Something like:

enum Role {
  case User,
  case Admin,
  ...
}

enum Action {
  case Order_Edit,
  case Order_Read,
  
  private const BY_ROLE = [
    Role::User => [self::Order_Read],
    Role::Admin => [self::Order_Read, self::Order_Edit],
  ];

  public function isAllowed(User $user) {
    return in_array($this, self::BY_ROLE[$user->role]);
  }
}



    Regards,

    -- 
    Rowan Tommins (né Collins)
    [IMSoP]

    -- 
    
    To unsubscribe, visit: https://www.php.net/unsub.php

---

## #112433 — Rowan Tommins — 2020-12-05T20:18:55+00:00

URL: https://externals.io/message/112417#112433 · In reply to: #112432

> I mean on mapping something to something else defined as a single assoc array constant.
> Something like:
>
> enum Role {
>    case User,
>    case Admin,
>    ...
> }
>
> enum Action {
>    case Order_Edit,
>    case Order_Read,
>    
>    private const BY_ROLE = [
>      Role::User => [self::Order_Read],
>      Role::Admin => [self::Order_Read, self::Order_Edit],
>    ];
>
>    public function isAllowed(User $user) {
>      return in_array($this, self::BY_ROLE[$user->role]);
>    }
> }


That example can be rewritten directly as a static method and match 
statement, which I think would look like this:

enum Action {
   case Order_Edit;
   case Order_Read;
   
   private static function byRole(Role $role) {
     return match($role) {
        Role::User => [self::Order_Read],
        Role::Admin => [self::Order_Read, self::Order_Edit],
     };
   }

   public function isAllowed(User $user) {
     return in_array($this, self::byRole($user->role));
   }
}


The scenario can also be modelled the other way around with case methods:

enum Action {
   public function isAllowed(User $user) {
      // Default case denying all, might be useful with a long list of cases
      return false;
   }

   case Order_Edit {
      public function isAllowed(User $user) {
         return $user->role === Role::Admin;
      }
   }
   case Order_Read {
      public function isAllowed(User $user) {
         return $user->role === Role::User
                || $user->role === Role::Admin;
      }
   }
}


Regards,

-- 
Rowan Tommins (né Collins)
[IMSoP]

---

## #112434 — Larry Garfield — 2020-12-05T20:20:23+00:00

URL: https://externals.io/message/112417#112434 · In reply to: #112432

> > […]
> 
> 
>     Could you give an example what you mean? Metadata on individual cases is 
>     supported by methods, which map more cleanly to things like interfaces, 
>     and the notion of each case as a singleton object, not a static class.
> 
> 
>     That said, I have a related question: can enum cases be used as the 
>     *value* of constants? e.g.:
> 
>     class OldMaid {
>          public const SUIT = Suit::Spades;
>          public const VALUE = CardValue::Queen;
>     }

At present, no.  They're "just" objects, and you can't assign an object to a constant.  Unfortunately I'm not sure how to enable that without making them not-objects, which introduces all sorts of other complexity.

> I mean on mapping something to something else defined as a single assoc 
> array constant.
> Something like:
> 
> enum Role {
>   case User,
>   case Admin,
>   ...
> }
> 
> enum Action {
>   case Order_Edit,
>   case Order_Read,
>   
>   private const BY_ROLE = [
>     Role::User => [self::Order_Read],
>     Role::Admin => [self::Order_Read, self::Order_Edit],
>   ];
> 
>   public function isAllowed(User $user) {
>     return in_array($this, self::BY_ROLE[$user->role]);
>   }
> }

Because of their object-ness, I think you'd have to use a weak map defined at runtime:

class AccessControl {
  private WeakMap $perms;
  public function __construct() {
    $this->perms = new WeakMap();
    $this->perms[Role::User] = [Action::Order_Read, Action::Order_Edit];
    $this->perms[Role::Admin] = [Action::Order_Read, Action::Order_Edit];
  }

  public function isAllowed($user, $action): bool {
    return in_array($action, $this->perms[$user->role]);
  }
}

--Larry Garfield

---

## #112435 — Larry Garfield — 2020-12-05T20:26:43+00:00

URL: https://externals.io/message/112417#112435 · In reply to: #112417

> Greetings, denizens of Internals!
> 
> Ilija Tovilo and I have been working for the last few months on adding 
> support for enumerations and algebraic data types to PHP.  This is a 
> not-small task, so we've broken it up into several stages.  The first 
> stage, unit enumerations, are just about ready for public review and 
> discussion.
> 
> The overarching plan (for context, NOT the thing to comment on right 
> now) is here: https://wiki.php.net/rfc/adts
> 
> The first step, for unit enumerations, is here:
> 
> https://wiki.php.net/rfc/enumerations
> 
> There's still a few bits we're sorting out and the implementation is 
> mostly done, but not 100% complete.  Still, it's far enough along to 
> start a discussion on and get broader feedback on the outstanding nits.
> 
> I should note that while the design has been collaborative, credit for 
> the implementation goes entirely to Ilija.  Blame for any typos in the 
> RFC itself go entirely to me.
> 
> *dons flame-retardant suit*
> 
> -- 
>   Larry Garfield
>   larry@garfieldtech.com

Thank you everyone for the feedback so far!  I've updated the RFC with a few changes, based on discussion here and elsewhere:

* Clarified that "enums behave like objects unless otherwise specified."  That should clarify a lot of edge case questions.  (Which is specifically one of the reasons to build off of objects.  We can inherit answers to most edge cases.)
* Primitive-backed Cases have been renamed to Scalar Enums, because "Primitive-backed" is just too clumsy to say or write all the time.
* There's now formal internal interfaces defined for Enum, UnitEnum, and ScalarEnum to define the methods mentioned.  These serve as both documentation and to allow user-space code to tell when a value it's dealing with is an enum, and a particular type of enum.  (And therefore what enum methods are available.)
* Added a `value()` method to ScalarEnum, to make it easier to get at the scalar equivalent arbitrarily.
* Fixed a bunch of typos.
* Updated the reflection section to have `getType()` return a ReflectionType rather than a bare string.

(The patch will be updated for the above shortly as Ilija's time allows.)

--Larry Garfield

---

## #112436 — Benjamin Morel — 2020-12-05T23:23:37+00:00

URL: https://externals.io/message/112417#112436 · In reply to: #112435

Thanks a lot for this RFC, Larry and Iliya! I can't imagine the amount of
thought and work put into this.
Enums are definitely a most-wanted PHP feature.

I played a bit with the early implementation, and love it so far. Here are
my thoughts on the RFC and the current implementation:

*Serialization*

I guess what it comes down to is whether / how easily a class can return
> an existing instance when asked to unserialize, rather than setting
> properties on an existing instance. That is, given the string
> "C:4:Suit:6:{Spades}" can the class definition return the appropriate
> singleton for Suits::Spades rather than a newly constructed object?
>
> If this proves tricky to implement, it would probably be better to
> forbid serialization than using the default object format and breaking
> the singleton-ness of the case objects.


+1, totally agree with this statement. That's the first thing I tried when
playing with the implementation, and noticed that serialization is not
supported:

    echo unserialize(serialize(Status::Active));
    Notice: `unserialize()`: Error at offset 0 of 42 bytes

I would definitely expect strict equality to be maintained on enum cases
even after unserialization!

*var_export()*

Currently, `var_export()` returns a __set_state() syntax:

    var_export(Status::Active);
    Foo\Bar\Status::Active::__set_state(array(
    ))

I guess this should just return Foo\Bar\Status::Active.

*Scalar Enums, ::cases()*

The implementation does not support these yet, so I haven't had a chance to
play with them.
I share Pierre R.'s concerns, though:

Does this mean that an enum can't have two cases with the same primitive
> value ? I would very much being able to do so, for example, when you
> change a name and want to keep the legacy for backward compatibility.


But I'd understand if you just disallow duplicate scalar values altogether,
this is probably the most sensible solution here.
Another idea that comes to mind is that cases() could return an iterator
instead of an array, having the cases as keys and the scalars as values,
but this would probably come as a surprise and be bad for DX.

*Enum & UnitEnum interfaces*

The implementation does not seem to support these yet. Taking the examples
from the RFC:

Suit::Hearts instanceof Enum; // true => Parse error: syntax error,
unexpected token "enum"
Suit::Hearts instanceof UnitEnum; // true => `FALSE` 


Best of luck with the RFC!
— Benjamin

---

## #112437 — Paul Crovella — 2020-12-06T00:17:30+00:00

URL: https://externals.io/message/112417#112437 · In reply to: #112419

>
>
> Dec 4, 2020 7:37:51 PM Paul Crovella <paul.crovella@gmail.com>:
>
> > […]
> The reasoning general comes down to one of 2 things:
>
> * they involve state, and enum cases have no state. They may get reintroduced with tagged unions, but for now methods relating to state would just be confusing.
> * we couldn't figure out what possible use they'd have (like static methods on cases, which without data are exactly the same as normal methods.)
>
> --Larry Garfield

Thank you for updating the RFC.

> enum cases have no state

Unless there's a bit left out from this RFC this is not completely
true, you've just limited them to annoying ways of working with data,
e.g. static variables.

> static methods on cases, which without data are exactly the same as normal methods

This is an argument against instance methods, which you kept, rather
than static methods, which you didn't. How is this division anything
but arbitrary?

> Constructors - Not relevant without data/state.
> Destructors - Not relevant without data/state.

This doesn't explain the need to remove them. What problem do they cause?

> Enum/Case constants - Not necessary as methods fulfill the same use case.

How is this specific to enums? Why is it necessary to add this
inconsistency to the language in this RFC? What happens to constants
inherited from interfaces?

> Enum/Case properties - Not necessary as methods fulfill the same use case, plus we want to avoid state.
> Dynamic properties - Avoid state. Plus, they're a bad idea on classes anyway.
> Magic methods except for those specifically listed below - Most of the excluded ones involve state.

Again, what is it particular to enums that necessitates adding these
inconsistencies to the language? I get that *you* want to avoid state,
but how is that an enum thing rather than a you thing? What happens to
properties gained from traits?

---

## #112438 — Rowan Tommins — 2020-12-06T15:11:47+00:00

URL: https://externals.io/message/112417#112438 · In reply to: #112437

> > […]
> Unless there's a bit left out from this RFC this is not completely
> true, you've just limited them to annoying ways of working with data,
> e.g. static variables.


I'm not sure what you mean here, but it sounds a bit like you're saying 
there are ways to emulate mutable state, so why bother restricting it?

The point is that since enum cases are singleton objects, any instance 
state would actually be global across the program, which is probably not 
what people would expect.


> > […]
> This is an argument against instance methods, which you kept, rather
> than static methods, which you didn't. How is this division anything
> but arbitrary?


On a singleton object, instance methods and late static binding are 
equivalent, but instance methods are probably more intuitive, since 
people are more used to writing code like "$this->suit->shape();" than 
"$this->suit::shape();"

I guess both could be supported, but other than more code style 
decisions to make, I can't think of what they'd add.


> Again, what is it particular to enums that necessitates adding these
> inconsistencies to the language? I get that*you*  want to avoid state,
> but how is that an enum thing rather than a you thing?


"Inconsistency" is a straw man here, because these are a brand new 
concept that already does things objects don't do. So let's flip it 
around: do you have any use cases for directly storing state on an enum 
case, remembering that such state would be unavoidably global?

Restricting them will likely make other desirable features easier - for 
instance, how would serialization work:

$a = Suit::Hearts;
$a->setColour(Colour::Pink);
$serialized = serialize($a); // does this serialize the instance state?
$a->setColour(Colour::Purple);
$b = unserialize($serialized);
assert($a === $b); // as discussed elsewhere, this should be true
assert($a->getColour() === $b->getColour());  // are they both pink? 
both purple?


Note that Larry's longer term plan is for "algebraic data types", 
including "tagged unions": https://wiki.php.net/rfc/adts Unlike 
straight-forward enum cases, these are not singletons, and each instance 
has its own associated state.


Regards,

-- 
Rowan Tommins (né Collins)
[IMSoP]

---

## #112442 — Larry Garfield — 2020-12-06T15:32:43+00:00

URL: https://externals.io/message/112417#112442 · In reply to: #112436

> Thanks a lot for this RFC, Larry and Iliya! I can't imagine the amount of
> thought and work put into this.
> Enums are definitely a most-wanted PHP feature.
> 
> I played a bit with the early implementation, and love it so far. Here are
> my thoughts on the RFC and the current implementation:

Yay!

> *Serialization*
> 
> I guess what it comes down to is whether / how easily a class can return
> > […]
> 
> 
> +1, totally agree with this statement. That's the first thing I tried when
> playing with the implementation, and noticed that serialization is not
> supported:
> 
>     echo unserialize(serialize(Status::Active));
>     Notice: `unserialize()`: Error at offset 0 of 42 bytes
> 
> I would definitely expect strict equality to be maintained on enum cases
> even after unserialization!

I've opened a task to just block serialization entirely for now. It's probably best to not support it at all than to have half-arsed buggy support, at least for now: https://github.com/Crell/enum-comparison/issues/46

> *var_export()*
> 
> Currently, `var_export()` returns a __set_state() syntax:
> 
>     var_export(Status::Active);
>     Foo\Bar\Status::Active::__set_state(array(
>     ))
> 
> I guess this should just return Foo\Bar\Status::Active.

Good point.  I've opened a task for that: https://github.com/Crell/enum-comparison/issues/47

> *Scalar Enums, ::cases()*
> 
> The implementation does not support these yet, so I haven't had a chance to
> play with them.
> I share Pierre R.'s concerns, though:
> 
> Does this mean that an enum can't have two cases with the same primitive
> > […]
> 
> 
> But I'd understand if you just disallow duplicate scalar values altogether,
> this is probably the most sensible solution here.
> Another idea that comes to mind is that cases() could return an iterator
> instead of an array, having the cases as keys and the scalars as values,
> but this would probably come as a surprise and be bad for DX.

Off hand, I'm not sure if any languages support duplicate enums.  I can see the use case for renaming, but I suspect it's going to just be too complicated to support in practice.  I'll make a note in the RFC that they must be unique.

> *Enum & UnitEnum interfaces*
> 
> The implementation does not seem to support these yet. Taking the examples
> from the RFC:
> 
> Suit::Hearts instanceof Enum; // true => Parse error: syntax error,
> unexpected token "enum"
> Suit::Hearts instanceof UnitEnum; // true => `FALSE` 

Yeah, we only added that to the RFC text an hour or three before you posted; Ilija has to catch up with the new text yet. :-)  Please give him some time.

--Larry Garfield

---

## #112443 — Larry Garfield — 2020-12-06T15:48:34+00:00

URL: https://externals.io/message/112417#112443 · In reply to: #112438

> Note that Larry's longer term plan is for "algebraic data types", 
> including "tagged unions": https://wiki.php.net/rfc/adts Unlike 
> straight-forward enum cases, these are not singletons, and each instance 
> has its own associated state.


I was going to respond, but I think Rowan summed it up here.  Stateful enums are called tagged unions, and that's a separate phase to keep the implementation and discussion focused for now.  We hope to add those in the future, but for now unit and scalar enums have a ton of value unto themselves.

--Larry Garfield

---

## #112447 — Paul Crovella — 2020-12-07T01:00:42+00:00

URL: https://externals.io/message/112417#112447 · In reply to: #112438

>
> 
> > […]
>
>
> I'm not sure what you mean here, but it sounds a bit like you're saying
> there are ways to emulate mutable state, so why bother restricting it?

Static variables don't emulate mutable state, they are mutable state.
My question is why go out of the way to restrict things arbitrarily,
particularly when the result is ineffective and just makes things suck
more. It's irrelevant extra work to annoy people with no gain.

> The point is that since enum cases are singleton objects, any instance
> state would actually be global across the program, which is probably not
> what people would expect.

Instance state being global is a well-known problem with singletons.
Maybe don't use singletons then. Or simply document them as was done
in the RFC. I'd prefer the former since singletons don't seem to buy
much here but problems, though maybe I'm missing something. In any
case why is static state being (kinda sorta) restricted along with it?

> > […]
>
>
> On a singleton object, instance methods and late static binding are
> equivalent, but instance methods are probably more intuitive, since
> people are more used to writing code like "$this->suit->shape();" than
> "$this->suit::shape();"
>
> I guess both could be supported, but other than more code style
> decisions to make, I can't think of what they'd add.

Consistency. Which reduces both the wtf factor when working with them
and edge cases for other features that interact with them.

> > […]
>
>
> "Inconsistency" is a straw man here, because these are a brand new
> concept that already does things objects don't do.

Inconsistency is in no way a straw man. This RFC has enums being
implemented as "fancy objects", based on classes, backed by objects,
with the results being treated as classes and objects in every way
except for.. well mostly a bunch of artificial restrictions that
appear to have nothing to do with enums at all.

> So let's flip it
> around: do you have any use cases for directly storing state on an enum
> case, remembering that such state would be unavoidably global?

No, that is a garbage standard that's been abused around here for too
long. This RFC is proposing to introduce inconsistencies into the
language, it's up to it to justify them. It is not up to me or anyone
else to come along and pass your personal code review just to ask for
that justification.

> Restricting them will likely make other desirable features easier - for
> instance, how would serialization work:

Like serialization already works with objects, including singletons.

> $a = Suit::Hearts;
> $a->setColour(Colour::Pink);
> $serialized = serialize($a); // does this serialize the instance state?
> $a->setColour(Colour::Purple);
> $b = unserialize($serialized);
> assert($a === $b); // as discussed elsewhere, this should be true
> assert($a->getColour() === $b->getColour());  // are they both pink?
> both purple?
>
>
> Note that Larry's longer term plan is for "algebraic data types",
> including "tagged unions": https://wiki.php.net/rfc/adts Unlike
> straight-forward enum cases, these are not singletons, and each instance
> has its own associated state.

Longer term plans are irrelevant except to avoid inadvertently
shutting the door on something. This RFC is up for discussion, and
will be up for voting, in isolation. It has to be able to stand on its
own.

> Regards,
>
> --
> Rowan Tommins (né Collins)
> [IMSoP]

---

Next page: https://externals.io/message/112417.md?page=2
