crell / enum-comparison Goto Github PK
View Code? Open in Web Editor NEWA comparison of enumerations and similar features in different languages
A comparison of enumerations and similar features in different languages
Sorry to raise this issue ^^ This has been the main criticism I've heard consistent across all platforms. As you know I have some mixed feeling on them. The benefits I can think of:
enum Suits {
public function color();
case Hearts {
public function color() {}
}
case Diamonds; // Not handled, error
}
While I think the state machine example is cool it's clearly an edge case that can be achieved with a normal class hierarchy.
Do you have any other benefits to add?
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.
The RFC currently says:
An Enumeration may have one or more case definitions, with no maximum, although at least one is required.
Any reason for requiring at least one case? All other language constructs allow empty bodies.
What is Enums type? So far it is not said that Enums introduce a new type, so are they expected to be of type object
?
If that's the case does it means that gettype()
function's output would be object
and there won't be a corresponding is_enum()
function, but rather we should use the is_object()
instead?
Don't you think it would be a little odd not to be able to distinguish between an object and enum value?
This compound keyword would eliminate the BC break of adding a new keyword. Although it looks a bit less pretty.
enum class Foo {
case Bar;
}
At least something we should consider / talk about.
$class = get_class(SomeEnum::SomeValue);
$obj = new $class;
What is $obj now? Does that throw?
What about new ReflectionClass(SomeEnum::SomeValue)->newInstanceWithoutConstructor();
?
What about $obj = unserialize(serialize(SomeEnum::SomeValue));
Esp. the latter case should work I think, and return the singleton object.
Right now, it produces a __set_state
-based output that populates a class. That's almost certainly wrong.
Instead, probably just produce the FQCN of the case so that = works correctly.
We have interfaces_exists
and trait_exists
, do we need enum_exists
to check whether a given class is an enum?
Python has had type hints for a while:
https://docs.python.org/3/library/typing.html
$a instanceof Suit::Spades; // true
I'm not entirely sure we really need support for the type Enum::Case
at this point because it's equivalent to ===
. It's only potentially useful when you want to call methods in some other context without checking/asserting the case first.
The RFC says:
Specifically, the following features of objects are not allowed on enumerations: Static methods
Any specific reason this shouldn't be allowed? Also, are we talking about enums or the enum cases?
Example where this would be useful:
enum Foo {
...
public static function fromRepresentation1($value) {
return match ($value) { ... };
}
public static function fromRepresentation2($value) {
return match ($value) { ... };
}
}
$foo = Foo::fromRepresentation1(1);
$foo = Foo::fromRepresentation2('foo');
We permit static methods … do we also permit constants (on enum decls, anyway not on cases; should be stated explicitly in the RFC though)? Should be symmetrical, but not sure about possible confusions.
And in the same thought train, static properties?
Are enums for the purpose of semantics and reflection classes?
And their singleton values then constants?
It has been mentioned multiple times that allowing constants in enums would be useful for creating aliases to avoid BC breaks.
enum Foo {
case Bar;
#[Deprecated]
const Baz = self::Bar;
}
Like with reflection I don't think we should differentiate between TARGET_CLASS
and TARGET_ENUM
/TARGET_CASE
. For example, we don't do that for interfaces/traits either.
Enum Cases may not implement interfaces themselves.
Since we allow implementing methods that are only available in a subset of the cases allowing to implement interfaces only for a subset of cases would probably also make sense.
What are accepted values on the LHS of =>? Is that supposed to work like instanceof, e.g. match type ($foo) { $enumclassname1 => ...,$enumclassname2 => ..., }
would be valid? And generally applicable to all objects / types (e.g. int
, stdClass
etc.)
https://wiki.php.net/rfc/enumerations currently says
Scalar equivalent values must be literals. Constants and constant expressions are not supported.
This was relaxed to allow -1
- -1
is not a literal
0 => AST_UNARY_OP [UNARY_MINUS] #1
expr => 1
The representation of PHP_INT_MIN is not a literal (9223372036854775808 is a float, and the negation of a float is a float)
php > var_export(PHP_INT_MIN);
-9223372036854775807-1
Related to #56
Do we really need type coercion? Especially since the RFC currently only allows one-sided coercion (from objects to scalars) I'd be happier dropping it altogether.
Do we auto-generate an EnumType::values()
method when the enum has only Unit Cases?
Pro: It's a common-enough use case. It's probably not too hard to do.
Con: Possibly interesting error handling when an Enum Type has Associable Cases, since then we can't have the method. Or the method has to throw an exception. Or something. Is the order locked at Lexical order or do we leave it undefined? What exactly is returned, strings or objects?
Discuss.
We should change var_dump output to enum(Foo::Bar)
. I'm not sure if this needs to be specified in the RFC.
From the RFC:
enum Suit implements Colorful {
case Hearts {
public function color(): string {
return "Red";
}
}; // Note the semi-colon here!
}
I think there should be no semi-colon here. This way we're consistent with methods with or with no body.
abstract class Foo {
function bar();
function baz() {}; // Syntax error, unexpected token ";"
}
Thoughts?
The enum currently proposes two new reflection types: ReflectionEnum
and ReflectionCase
.
There's a very very large overlap with ReflectionClass
for both of these since they are in fact just classes. Thus I suggest also actually making them subclasses of ReflectionClass
.
ReflectionEnum::[hasCase|getCases|getCase]
hasConstant|getConstants|getConstant
ReflectionCase::getEnum
getParentClass()
Note that interfaces and traits also don't have separate reflection classes since they are so similar to classes. I propose doing the same here.
The following object functionality is available, and behaves just as it does on any other object:
- ...
- __get, __call, __serialize, __deserialize, and __invoke magic methods
Does it actually make sense to __serialize
and __deserialize
? I see no point in this, you're not allowed to store properties on the enum anyway.
When are they equal?
When their contents match weakly? strictly?
The RFC currently lists __get
as an allowed method (but not __set
). given that properties are forbidden, what is the reason to allow magic properties ?
Will it be possible to write enum A extends B {}
where B
is another enum? - Yes, I see no fundamental reason why that should not work out.
Can we use traits in enums and cases? - Yes, I would say so.
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”.)
I'm afraid this could result in some rounding errors.
For scalar-backed enums, add a value() method that returns the corresponding value.
For unit enums, the method doesn't exist and so will error out like any other missing method. Possibly with a more targeted error message.
Should we allow private
cases? How would that work with pattern matching?
Based on the discussion in R11.
Scalar IMO is not a very descriptive name. 1. Not all scalars are allowed 2. If we ever allow non-scalars (like enums) then the term will also be wrong.
Since our property is called value
ValueEnum
might make sense although I think that will become confusing as soon as we have ADTs. I'd prefer RawEnum
(also renaming value
to raw
).
@Crell WDYT?
It also becomes a natural and easy way to implement Monads in user space, and both Haskell and Rust do exactly that in their core libraries. (I'm not clear if Swift does, but you can absolutely do so yourself).
Swifts Optional
type is also just an enum.
@frozen enum Optional<Wrapped>
They just have fancy syntax that means the same thing (e.g. Int?
=> Optional<Int>
).
This comparison survey states: Enums do not support constructors. (Or rather, the constructor is private, so you cannot pass parameters to it.)
Official documentation disagrees: https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html
Scroll down to public enum Planet
, there is a constructor with parameters, which, naturally, can be overloaded.
This is a huge oversight.
Comment from Nikita:
Rather than WeakMap, the possibly more natural choice for using enum keys
is SplObjectStorage. Of course, SplObjectStorage, like anything that is
part of SPL, has some peculiarities... Of course, just allowing them as
array keys would be ideal, but I agree that this should not be covered by
this RFC. This is something I may look into.
I agree with Nikita. SplObjectStorage seems more fitting as WeakMap is specifically designed for avoiding reference counting. It's not really relevant for constants since they will not be released until the end of the script but SplObjectStorage still seems like the more obvious choice.
Right now, serialization is either a loophole around === working, or a fatal. Unless we can be certain how it's going to behave, we may want to just forbid it for now, at least until we figure out how it should work better.
I don't think unserialize(serialize(Suit::Clubs));
is ever going to really play nice without a ton of work, so better to not have it half-work.
Public, private, and protected methods. (Protected methods are effectively identical to private as inheritance is not allowed.)
This is true only for enum cases but not the enums themselves.
enum Foo {
case Bar {
public function baz() {
$this->qux(); // Error
}
}
private function qux() {}
}
interface Enum
interface UnitEnum extends Enum { public function cases() }
interface ScalarEnum extends Enum { public function value(), public static function from() }
interface TaggedEnum extends Enum
Based on this discussion. We need to prevent Foo::Bar[0]
in const expressions because that would allow you to run arbitrary code in a constant expression (through offsetGet
). This should probably be mentioned in the RFC.
Here's the tests, maybe that will make more sense:
iluuu1994/php-src@4f4c314
README.md says
I deliberately excluded languages with no native enum support. Languages such as Javascript, Go, or Ruby do not (as far as I can tell) have any native enumerations, although there are various hacky ways to simulate them in user space. That is not of interest to us at this time.
Scala 3, due to be released in a few months, will in fact have language support. http://dotty.epfl.ch/docs/reference/enums/enums.html
However Scala 2.x does not. Enumeration
is a class in the standard library that uses reflection and takes advantage of several Scala language features to make it easy to define simple enumerations.
People often use sealed traits + case objects instead (which is basically how ADTs are done in Scala, and as linked above in Scala 3 enum/ADT support desugars to that.)
A popular approach is to use https://github.com/lloydmeta/enumeratum#usage, which uses Scala's compile-time metaprogramming vs. runtime reflection to provide enum support as a library.
Again, Scala 3 builds on that approach of using sealed trait + case objects / case classes, which is how ADTs are modeled as well, and provides first-class syntax for enums and ADTs. However enums can be compiled as java.lang.Enums.
So Scala really bridges your distinction of "Fancy Object" languages and "ADT" languages. ADTs are objects and classes that can be safely pattern matched. In Scala the syntax encourages you to think of classes as parameterized objects. And the new enum feature in Scala 3 generalizes over ADTs and enums. When some cases are parameterized you simply lose the capabilities that no longer make sense (like getting a list of all values) and keep everything else.
This should be added to future scope.
Quoting example from rightfold: https://chat.stackoverflow.com/transcript/message/50502258#50502258
enum Suit {
use EnumString, EnumInt; // these traits use the AsString and AsInt attributes
#[AsString('H'), AsInt(1)] case Hearts;
#[AsString('C'), AsInt(2)] case Clubs;
#[AsString('D'), AsInt(3)] case Diamonds;
#[AsString('S'), AsInt(4)] case Spades;
}
I think that's a good usage of attributes - and having a good usage available, we should probably allow them.
I think it's sort of okay to bring the pattern matching as a separate RFC, but a well thought out design for pattern matching should exist beforehand, so that they can be nicely integrated at some future point without having to realize fundamental shortcomings in enums with associated values later on.
I wish to see couple a solid examples of it and why they would work out.
Is there any case or reason why an enum should ever contain protected or private contents?
I consider an enum to be a fully transparent object without hidden state - immutable and fully open. Enums are data, not state.
As such I would also drop the visibility modifier from associated values argument lists.
WTF, traits. 😄
How easy is it to only allow traits that have no properties? We want to avoid those becoming a back-door way to introduce state.
That is the question.
case Suit {
case hearts(String)
case diamonds(String)
case clubs(String)
case spades(String)
}
I get that every language uses the same example for better comparison. It would be easier to demonstrate that each case holds its own set of associated values with a different example where each case holds different arity and type of assoc values.
How do enum interact with the object
typehint ? Being backed by objects, a naive implementation would probably allow enums to satisfy such a typehint. But that might be counter-intuitive for devs not knowing the internal implementation.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.