Crell / enum-comparison

A comparison of enumerations and similar features in different languages
80 stars 7 forks source link

Future scope? Combining multiple (non-value-associated) enum values #19

Closed bwoebi closed 3 years ago

bwoebi commented 4 years ago

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.

iluuu1994 commented 4 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?

bwoebi commented 4 years ago

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.

bwoebi commented 4 years ago

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.

Crell commented 4 years ago

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.

iluuu1994 commented 4 years ago

If I understand @bwoebi correctly EnumSet would make adding additional logic to enums unnecessary.

bwoebi commented 4 years ago

Yeah, should be the case @iluuu1994

iluuu1994 commented 3 years ago

IMO EnumSet is the better approach.

iluuu1994 commented 3 years ago

Actually, can we specify this in the RFC? (Future scope)

Crell commented 3 years ago

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.

iluuu1994 commented 3 years ago

Something like this (although probably significantly less complicated).

bwoebi commented 3 years ago

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)

iluuu1994 commented 3 years ago

This is mentioned now.