Closed bwoebi closed 3 years ago
C# does so via the Flags
attribute. In that case we would need enums backed by raw values which we avoided in this RFC. The question is, can that be added in the future or would the RFC currently make that impossible?
Why would we need backing by raw values?
I think the RFC should not make it impossible, but I'm mentioning it so that we can think about whether the RFC would restrain it.
C# does so via the Flags attribute
Yeah, I know, and I do not particularly like it, because you still put the name of the enum as type in your signature and then you get bitten because you forgot about the Flag-possibility and thought your switch were exhaustive. Hence I'd prefer something like EnumSet<Perm>
to be more clear and obvious at that point.
From my research, it seemed like that feature only existed in languages/cases where the enum cases were primitive-backed, and thus you could assign them to bitmasks, and compound enum cases fall out of that naturally. Since we're not making them primitive-backed, I figured we would just accept not having that ability. I'm not against it, I just figured it was a worthy sacrifice for robust ADTs that weren't overly complicated with too many conflicting syntaxes.
If we can include them without making it overly complicated, though, I'm totally on board with union enums.
If I understand @bwoebi correctly EnumSet
would make adding additional logic to enums unnecessary.
Yeah, should be the case @iluuu1994
IMO EnumSet
is the better approach.
Actually, can we specify this in the RFC? (Future scope)
If you can give me a better description of what EnumSet is, sure. So far people are tossing the term around but I'm not really clear on what it means.
Something like this (although probably significantly less complicated).
Bitwise operation on enum values (or EnumSet) shall yield an EnumSet, it consisting of the union of all enum values resulting.
An EnumSet of a single value decays to the singular enum value it contains.
Examples:
// given:
enum E {
case A;
case B;
case C;
}
$set = E::B | E::B;
var_dump($set); // object(EnumSet<E>) { [0] => E::A, [1] => E::B }
var_dump($set | E::C); // object(EnumSet<E>) { [0] => E::A, [1] => E::B, [2] => E::C }
var_dump($set & (E::A | E::C)); // E::A
var_dump(~E::B); // object(EnumSet<E>) { [0] => E::A, [1] => E::C }
function func_accepting_set(EnumSet<E> $value) {}
func_accepting_set(E::A); // covariant to EnumSet<E>, thus valid
var_dump(count($set)); // int(2)
# use count($set) == 0 to check whether a set is empty for example
So essentially, EnumSet is an internal (final) class with overloaded bitwise operators able to operate on enum values, implementing Countable and Traversable.
There probably also should exist an EnumSet::EMPTY
or such, to explicitly pass around an empty set. (without having to do a hack like E::A & E::B
)
This is mentioned now.
C# has the nice ability to do
$permissions = Perm::READ | Perm::EXEC
to describe a set of combinations of values; not every enum value is mutually exclusive.Do we want to keep this possibility in mind while designing enums, or do we want to reject that entirely?
As I think about it right now, I feel like we need a proper
EnumSet<? is Enum>
generic for that so that we can distinguish these from simple values in our types.