Hi,
Is there any interest in having enums as class constants?
I'm often finding cases where I would like to have an enum inside of a
class, but don't want a free-floating enum that's basically like
another class.
When dealing with state, it's nice to have a human readable const to
represent that state, but I always feel like they should be grouped
together.
For example:
class SSHClient {
public const COMMAND_RESULT_SUCCESS = 0;
public const COMMAND_RESULT_FAILURE = 1;
public const COMMAND_RESULT_UNKNOWN = 2;
public const COMMAND_RESULT_TIMEOUT = 3;
// ...
}
These constants would make sense as an enum, but they make no sense
outside of the SSHClient class that uses them.
It seems that enums would be useful as class constants. There's a lot
of cases where a class implements a state machine and needs statuses,
but those status flags should be local to the class, not shared between
classes.
Example:
class SSHClient {
public const enum CommandResult
{
case Success;
case Failure;
case Unknown;
case Timeout;
}
// ...
}
// Usage:
SSHClient::CommandResult::Success
Hi,
Is there any interest in having enums as class constants?
I'm often finding cases where I would like to have an enum inside of a
class, but don't want a free-floating enum that's basically like
another class.When dealing with state, it's nice to have a human readable const to
represent that state, but I always feel like they should be grouped
together.
Yes, I would very much like that.
-Mike
P.S. See related: https://externals.io/message/107995 <https://externals.io/message/107995
Hi Nick,
Is there any interest in having enums as class constants?
I'm often finding cases where I would like to have an enum inside of a
class, but don't want a free-floating enum that's basically like
another class.
...<snip>...
class SSHClient {
public const enum CommandResult
{
case Success;
case Failure;
case Unknown;
case Timeout;
}// ...
}// Usage:
SSHClient::CommandResult::Success
I feel this topic could be maybe more broad and be called "nested classes"
that are already supported in multiple languages: Java, Swift, Python, C#,
C++, JavaScript, etc.
The syntax you showed is usually identical with what other languages use,
except that probably the const is unnecessary.
The nested class can have visibility as sometimes having it private makes
sense.
Accessing it through ::
is probably fine, but a deeper look at the
grammar might be necessary.
The nested class would have access to parent class private properties and
methods.
I also mentioned this topic on the subject of defining a type in an
autoloader compatible way.
And indeed, a type could also be defined nested in a class if we want to
support that as well.
Now, this feature is not simple, and I think it needs proper sponsorship
from someone experienced with internals.
Regards,
Alex
Hi Nick,
Is there any interest in having enums as class constants?
I'm often finding cases where I would like to have an enum inside of a
class, but don't want a free-floating enum that's basically like
another class....<snip>...
class SSHClient {
public const enum CommandResult
{
case Success;
case Failure;
case Unknown;
case Timeout;
}// ...
}// Usage:
SSHClient::CommandResult::Success
I feel this topic could be maybe more broad and be called "nested
classes" that are already supported in multiple languages: Java, Swift,
Python, C#, C++, JavaScript, etc.The syntax you showed is usually identical with what other languages
use, except that probably the const is unnecessary.
The nested class can have visibility as sometimes having it private
makes sense.
Accessing it through::
is probably fine, but a deeper look at the
grammar might be necessary.
The nested class would have access to parent class private properties
and methods.I also mentioned this topic on the subject of defining a type in an
autoloader compatible way.
And indeed, a type could also be defined nested in a class if we want
to support that as well.Now, this feature is not simple, and I think it needs proper
sponsorship from someone experienced with internals.Regards,
Alex
I agree with Alexandru. Since enums are 90% syntactic sugar over classes, "inner enums" would be 80% of the way to "inner classes". And I would be in favor of inner classes. :-) There's a lot of potential benefits there, but also a lot of edge cases to sort out regarding visibility, what is allowed to extend from what, etc. But that would support inner enums as well.
Based on our sibling languages (Java, Kotlin, C#, etc.), the syntax would likely be something like:
class Outer
{
private string $foo;
public function __construct(protected Sort $order) {}
enum Sort
{
case Asc;
case Desc;
}
class Inner
{
public function __construct(private string $baz) {}
}
private class HIdden
{
public function __construct(private string $baz) {}
}
}
Which enables:
$case = Outer::Sort::Asc;
$o = new Outer($case);
$i = new Outer::Inner('beep');
$h = new Outer::Hidden('beep'); // Visibility error
I would have to research to see if other languages did this, but one option would be to allow an inner class to extend an outer class even if it's final, which would essentially give us sealed classes for free:
final class Outer
{
class InnerA extends Outer {}
class InnerB extends Outer {}
class InnerC extends Outer {}
}
// But this is still not OK:
class Sibling extends Outer {}
Note: I have no idea how difficult/complicated this would be, but I would be in favor of exploring it.
--Larry Garfield
Hi Nick,
Is there any interest in having enums as class constants?
I'm often finding cases where I would like to have an enum inside of a
class, but don't want a free-floating enum that's basically like
another class....<snip>...
class SSHClient {
public const enum CommandResult
{
case Success;
case Failure;
case Unknown;
case Timeout;
}// ...
}// Usage:
SSHClient::CommandResult::Success
I feel this topic could be maybe more broad and be called "nested
classes" that are already supported in multiple languages: Java, Swift,
Python, C#, C++, JavaScript, etc.The syntax you showed is usually identical with what other languages
use, except that probably the const is unnecessary.
The nested class can have visibility as sometimes having it private
makes sense.
Accessing it through::
is probably fine, but a deeper look at the
grammar might be necessary.
The nested class would have access to parent class private properties
and methods.I also mentioned this topic on the subject of defining a type in an
autoloader compatible way.
And indeed, a type could also be defined nested in a class if we want
to support that as well.Now, this feature is not simple, and I think it needs proper
sponsorship from someone experienced with internals.Regards,
AlexI agree with Alexandru. Since enums are 90% syntactic sugar over classes, "inner enums" would be 80% of the way to "inner classes". And I would be in favor of inner classes. :-) There's a lot of potential benefits there, but also a lot of edge cases to sort out regarding visibility, what is allowed to extend from what, etc. But that would support inner enums as well.
From recently looking into this for totally unrelated reasons, nested enums would be far easier to implement on a grammar level. Enums also have some constraints that make it simpler than the general “nested classes,” such as rules regarding inheritance.
As for the actual implementation, it’ll be the edges that kill you.
I would recommend just doing enums, and keep the scope smaller.
I would have to research to see if other languages did this, but one option would be to allow an inner class to extend an outer class even if it's final, which would essentially give us sealed classes for free:
final class Outer
{
class InnerA extends Outer {}
class InnerB extends Outer {}
class InnerC extends Outer {}
}// But this is still not OK:
class Sibling extends Outer {}Note: I have no idea how difficult/complicated this would be, but I would be in favor of exploring it.
--Larry Garfield
Swift allows nested classes to extend their parent. It does not allow nested classes to extend a final parent. Visibility modifiers can be applied and work as expected. Basically, types also function as the equivalent of a namespace. More broadly, any type can be nested in any other type, so you could certainly do something weird like:
class A {
enum E {
class C: A {}
case a(A)
case c(C)
}
}
let e = A.E.c(A.E.C())
However, you could implement your sealed class example by having a private constructor:
class Parent {
final class Child : Parent {
override init() { //within lexical scope of Parent, so can see Parent's private members
super.init()
}
}
private init() {}
}
let parent = Parent() //'Parent' initializer is inaccessible due to 'private' protection level
let child = Parent.Child() //ok
Swift also has a fileprivate visibility, so the same could be accomplished with a fileprivate init on Parent with a non-nested Child in the same file.
-John
<snip>Hi Nick,
Is there any interest in having enums as class constants?
I'm often finding cases where I would like to have an enum inside of a
class, but don't want a free-floating enum that's basically like
another class....<snip>...
class SSHClient {
public const enum CommandResult
{
case Success;
case Failure;
case Unknown;
case Timeout;
}// ...
}// Usage:
SSHClient::CommandResult::Success
I feel this topic could be maybe more broad and be called "nested
classes" that are already supported in multiple languages: Java, Swift,
Python, C#, C++, JavaScript, etc.I agree with Alexandru. Since enums are 90% syntactic sugar over classes,
"inner enums" would be 80% of the way to "inner classes". And I would be
in favor of inner classes. :-) There's a lot of potential benefits there,
but also a lot of edge cases to sort out regarding visibility, what is
allowed to extend from what, etc. But that would support inner enums as
well.Based on <snip>....
--Larry Garfield
Hello Larry,
I feel obliged to remind about the 80/20 rule where the last 20% of
progress ends up being 80% of all the work. And from the discussion it's
already looking like there are some major questions and caveats and engine
problems that are gonna rear their ugly heads. I'm more in favour starting
with somewhat self-contained features and steadily work to expand on them
as people put the effort into it. The same as was done with the type
system. You can lay the proper foundation now, so it's not blocking future
expansion, but I really do not think full embedded classes are gonna be a
short endeavor - probably multiple years if not half a decade going by
prior record on features of this size.
Arvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius
Hello Larry,
I feel obliged to remind about the 80/20 rule where the last 20% of
progress ends up being 80% of all the work. And from the discussion
it's already looking like there are some major questions and caveats
and engine problems that are gonna rear their ugly heads. I'm more in
favour starting with somewhat self-contained features and steadily work
to expand on them as people put the effort into it. The same as was
done with the type system. You can lay the proper foundation now, so
it's not blocking future expansion, but I really do not think full
embedded classes are gonna be a short endeavor - probably multiple
years if not half a decade going by prior record on features of this
size.Arvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius
To go off on this tangent for a bit...
There's a difficult balancing act to be had here. On the one hand, yes, design small and grow as you get feedback makes sense. On the other hand, if you don't think through the whole plan, those early steps could end up being blockers for expansion, not a benefit. The most obvious example, readonly was billed as a building block toward aviz. It ended up making aviz harder, and it not passing the first time around. It remains to be seen if first-class callables end up getting in the way of full PFA in the future. The piecemeal way in which anonymous functions have been added over time has led to a lot of rough edges, and made discussions about smoothing them over harder (eg, the auto-closure-long-callable RFC).
When those first steps are effectively set in stone forever, rather than something you can adjust based on future feedback, "YAGNI" becomes an actively harmful approach to system design.
--Larry Garfield
Hi,
Is there any interest in having enums as class constants?
I'm often finding cases where I would like to have an enum inside of a
class, but don't want a free-floating enum that's basically like
another class.When dealing with state, it's nice to have a human readable const to
represent that state, but I always feel like they should be grouped
together.For example:
class SSHClient {
public const COMMAND_RESULT_SUCCESS = 0;
public const COMMAND_RESULT_FAILURE = 1;
public const COMMAND_RESULT_UNKNOWN = 2;
public const COMMAND_RESULT_TIMEOUT = 3;// ...
}
These constants would make sense as an enum, but they make no sense
outside of the SSHClient class that uses them.It seems that enums would be useful as class constants. There's a lot
of cases where a class implements a state machine and needs statuses,
but those status flags should be local to the class, not shared between
classes.Example:
class SSHClient {
public const enum CommandResult
{
case Success;
case Failure;
case Unknown;
case Timeout;
}// ...
}// Usage:
SSHClient::CommandResult::Success
As a heavy Enum users, I'd love this.
Other said about going all the way as embedded classes, but I would voice
the opinion that it's gonna feature creep like crazy and probably has a lot
more work in it than anyone imagines at the moment - just look on how long
it took work out aviz. Enums are very limited as classes go, so it would be
probably far easier to implement them. If implementation is done in a way
where it's easy to expand in the future, I don't see a need to delay for a
bigger scope.
That being said, I do have to ask about having methods and being able to
implement an interface and add traits to the embedded enum?
All of my enums implement this interface
<?php
declare(strict_types=1);
namespace App\Interface;
use BackedEnum;
use Symfony\Contracts\Translation\TranslatableInterface;
interface TranslatableEnumInterface extends TranslatableInterface,
BackedEnum {}
and subsequently look like this:
<?php
declare(strict_types=1);
namespace App\Enum;
use App\Interface\TranslatableEnumInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
enum ChannelTypeEnum: int implements TranslatableEnumInterface
{
case EMAIL = 1;
case WHATSAPP = 2;
case BOTH = 3;
public function trans(TranslatorInterface $translator, string $locale =
null): string
{
return match ($this) {
self::EMAIL => $translator->trans('Email', locale: $locale),
self::WHATSAPP => $translator->trans('Whatsapp', domain:
'non_translatable', locale: $locale),
self::BOTH => $translator->trans('Both', locale: $locale),
};
}
}
Honestly, I would really like to do this right there in class embedded, as
having a separate file really is overkill :)
--
Arvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius