On Sun, Dec 6, 2020, at 7:00 PM, Paul Crovella wrote:

Hi Paul.  Although we're on hold for a bit while Ilija makes some changes in 
direction (see previous email), I wanted to respond to your concerns in 
particular because it sounds like you're misunderstanding the scope and roadmap 
of what we're trying to do.

Enumerations, as a general concept, are stateless.  Or rather, the idea of 
state doesn't even apply to them.  The integer 5 doesn't have any state of its 
own.  All instances of the integer 5 are logically identical to all other 
instances of the integer 5.

Enumerations are a way to allow users to define that kind of "list of allowed 
values" where each value is always identical to itself.  Depending on the 
language they often have the ability to define additional operations on that 
custom type, which are (from what I've found) nearly always implemented in a 
method-like syntax for simplicity.  In most languages that do so it's 
straightforward because enums are built on top of objects for convenience, but 
for instance C# doesn't use objects for enums but still has a way to attach 
method-like functions to them.  (See the survey review I did that's linked from 
the RFC.)

That the PHP RFC for enums uses objects internally is incidental.  A few people 
suggested that we use an entirely new zval type for it, which could no doubt be 
done and achieve the same end syntax but would be a lot more work internally to 
do.

So if you're defining, say, an enum for cardinal directions (North, South, 
East, West), There are literally only 4 possible values for that type, and 
those values must always be equal to themselves.  Attaching "5 miles" to 
"South" in one script and "2 km" to "South" in another script is nonsensical, 
just like attaching "feet" to the number 5 and later changing it to "fingers" 
just doesn't make any sense at all.

That's why, although we are building enums on top of objects, they cannot and 
must not have mutable state.  They can have predefined methods on them that 
return a static value (such as a label() method), but that doesn't violate the 
"5 always === 5" rule.  So anything that offers a way to violate South === 
South cannot be allowed.  If you want that, then you don't want enums; you want 
classes.  Don't get distracted by the word "singleton" here.  That's an 
implementation detail, not something the user is ever exposed to.

It is true that, if you're attaching operations to enum cases that have no 
state, a method, a static method, and a constant are often interchangeable.  A 
constant is strictly less capable since it cannot have logic, but a method and 
static method are functionally equivalent.  The reason for forbidding static 
methods and constants is largely simplicity.  Instance methods can cover all of 
those use cases, so it's only one mechanism to code and only mechanism for 
users to have to think about.  "Should I use mechanism X or Y when they are 
virtually identical?" is a question that is best avoided.  Too, it's a lot 
easier to add features later if we find they would be useful than to take them 
away if we find they're problematic.

For example, the change I just discussed in my previous email about switching 
to a single class with instances as constants means that static methods can 
only be on the enum (Suit), because there is no per-case-class to put them on 
(Diamonds).  The same is true of per-instance constants.  If we'd promised that 
those things worked, we wouldn't be able to make this change.

If when the dust settles there's a good reason to expand the functionality 
further in ways that still don't violate "South === South," (such as adding 
constants to the enum type itself, perhaps) those can be considered at that 
time.

As both Rowan and I mentioned, the long-term plan includes future RFCs for 
other functionality such as tagged unions.  If you want state, then what you 
want is not an enum but a tagged union.  For whatever reason, most languages 
with tagged unions seem to build them into and onto enums.  I'm not entirely 
sure why, and I'm honestly not 100% convinced that's the right approach in PHP, 
but that's a debate for a future RFC.  Within the scope of this RFC, enums must 
always obey "South === South", and thus must be stateless.  When we get to 
tagged unions we can and likely will debate how state works there.

> 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.

This is true up to a point.  Yes, each RFC should be able to stand on its own, 
but that doesn't mean it must be everything and the kitchen sink.  Trying to 
add enums, tagged unions, pattern matching, overridable identity, and generics 
all at once (since all of those *are* related to each other) would result in a 
massive and unreviewable patch, a massive and unreviewable RFC, and people not 
able to follow all the moving parts well enough to comment on them 
intelligently.  That would almost certainly fail.

Explicitly setting up a series of RFCs like we're doing is, as far as I am 
aware, novel for PHP.  It's the correct way to handle a larger task like this, 
however, especially when there clear division points that "chunk" the work in 
natural ways.  Even if enums in this RFC are the only thing that passes, that's 
still a major win for PHP.  It's not as big a win as tagged unions with robust 
pattern matching and generics on the side would be, but it's still a big step 
forward.  By breaking it into chunks, we make it more likely that as much as 
can be done will get done, and approved, and into the language, even if the end 
result is the same feature set when 8.1.0 gets tagged.

--Larry Garfield

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php

Reply via email to