[RFC][Draft] Sealed Classes

php.internals

azjezz

5 years ago
Hello Internals, I'm sending this email to open discussion about sealed classes, interfaces, and traits feature for PHP 8.1. I have create a Draft RFC here: https://wiki.php.net/rfc/sealed_classes A major concern for few people have been the syntax, in which it introduces 2 new keywords into the languages, therefor, i have added a section about alternative syntax which could be used to avoid this problem. Regards, Saif.

azjezz

5 years ago
A major behavior i wanted to discuss is how should sealed interfaces work, and specially around `Throwable` which is currently sealed to only `Error` and `Exception`, but allows other interfaces to extend it, and other classes to implement it as long as they extend `Error` or `Exception` ( or another class that does so ). As per the current proposal, if `Throwable` is to be declared `sealed`, permitting only `Error` and `Exception`, it won't be possible to extend it, resulting in a major BC-break, considering that too many libraries extend this interface in their own `ExceptionInterface`. To work around this, there's two solution: 1. don't declare `Throwable` sealed and keep it as a special case. 2. interfaces declared sealed, can be extended by other interfaces, however, classes that implement either the sealed interface directly, or via another interface, have to extend one of the classes the sealed interface permits. The second solution will allow declaring `Throwable` sealed, and would bring complete consistency between internally sealed symbols, and user land, without any BC breaks, and IMHO, it doesn't hurt as at the end, no one will be able to implement `Throwable` without extending the permitted classes. Note: with the second solution, if an interface is sealed and permits only 2 classes which are final, and you extend that interface into another, there's no way you can actually implement your new interface, and i will be as if you just declared a final interface. ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ On Saturday, April 24, 2021 11:55 AM, Saif Eddin Gmati <azjezz@protonmail.com> wrote:

Larry Garfield

5 years ago
On Sat, Apr 24, 2021, at 6:00 AM, Saif Eddin Gmati wrote:
> A major behavior i wanted to discuss is how should sealed interfaces > work, and specially around `Throwable` which is currently sealed to > only `Error` and `Exception`, but allows other interfaces to extend it, > and other classes to implement it as long as they extend `Error` or > `Exception` ( or another class that does so ). > > As per the current proposal, if `Throwable` is to be declared `sealed`, > permitting only `Error` and `Exception`, it won't be possible to extend > it, resulting in a major BC-break, considering that too many libraries > extend this interface in their own `ExceptionInterface`. > > To work around this, there's two solution: > > 1. don't declare `Throwable` sealed and keep it as a special case. > 2. interfaces declared sealed, can be extended by other interfaces, > however, classes that implement either the sealed interface directly, > or via another interface, have to extend one of the classes the sealed > interface permits. > > The second solution will allow declaring `Throwable` sealed, and would > bring complete consistency between internally sealed symbols, and user > land, without any BC breaks, and IMHO, it doesn't hurt as at the end, > no one will be able to implement `Throwable` without extending the > permitted classes. > > Note: with the second solution, if an interface is sealed and permits > only 2 classes which are final, and you extend that interface into > another, there's no way you can actually implement your new interface, > and i will be as if you just declared a final interface. > > > ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ > On Saturday, April 24, 2021 11:55 AM, Saif Eddin Gmati > <azjezz@protonmail.com> wrote: > > > Hello Internals, > > > > I'm sending this email to open discussion about sealed classes, interfaces, and traits feature for PHP 8.1. > > > > I have create a Draft RFC here: https://wiki.php.net/rfc/sealed_classes > > > > A major concern for few people have been the syntax, in which it introduces 2 new keywords into the languages, therefor, i have added a section about alternative syntax which could be used to avoid this problem. > > > > Regards, > > > > Saif.
1) I am generally supportive of this concept. 2) For exceptions, I'm honestly fine with Throwable just being weird. It already is, so letting it stay weird doesn't hurt anything. 3) The syntax examples mention traits, but nothing else in the spec talks about traits. What does sealed mean on a trait? I assume "can only be 'use'd by these classes", but that should be explicit. 4) I'm on team Attributes for the syntax, for two main reasons. One, it eliminates the need for a new keyword entirely. It would only require defining a new built-in class, and that class could be namespaced (per the likely-to-pass namespace policy) so that it is unlikely to conflict with anything at all. Two, I find it visually better as it it is not part of the type system per se, but a sort of meta-type system. Which is exactly where Attributes fit. (Basically what Levi said.) 5) If this passes, then combined with Nikita's "new in intializiers" RFC (https://wiki.php.net/rfc/new_in_initializers) it would effectively give us ADTs/Union Types by a different syntax. One could argue which syntax is better or more convenient (I'm not sure which I favor myself), but I'm just calling out that this would give us effectively the same result in terms of capability. --Larry Garfield

Michał Brzuchalski

5 years ago
Hello Saif sob., 24 kwi 2021, 13:00 użytkownik Saif Eddin Gmati <azjezz@protonmail.com> napisał:
> A major behavior i wanted to discuss is how should sealed interfaces work, > and specially around `Throwable` which is currently sealed to only `Error` > and `Exception`, but allows other interfaces to extend it, and other > classes to implement it as long as they extend `Error` or `Exception` ( or > another class that does so ). > > As per the current proposal, if `Throwable` is to be declared `sealed`, > permitting only `Error` and `Exception`, it won't be possible to extend it, > resulting in a major BC-break, considering that too many libraries extend > this interface in their own `ExceptionInterface`. > > To work around this, there's two solution: > > 1. don't declare `Throwable` sealed and keep it as a special case. > 2. interfaces declared sealed, can be extended by other interfaces, > however, classes that implement either the sealed interface directly, or > via another interface, have to extend one of the classes the sealed > interface permits. > > The second solution will allow declaring `Throwable` sealed, and would > bring complete consistency between internally sealed symbols, and user > land, without any BC breaks, and IMHO, it doesn't hurt as at the end, no > one will be able to implement `Throwable` without extending the permitted > classes. >
I like the idea of sealed class along with proposed keywords which are similar to already existing in other languages like Java. Personally I'd go with the second option. Speaking of Attributes I prefer not to use an Attribute for any particular language feature which expects input arguments to be a valid class or interface name for two reasons: first because there is no effective way to restrict input string to be a valid class or interface name and second that it'd require passing strings which means in most cases passing class or interface name with magic ::class constant read. Cheers, Michał Marcin Brzuchalski

Pierre

5 years ago
Le 24/04/2021 à 12:55, Saif Eddin Gmati a écrit :
> Hello Internals, > > I'm sending this email to open discussion about sealed classes, > interfaces, and traits feature for PHP 8.1. > > I have create a Draft RFC here: > https://wiki.php.net/rfc/sealed_classes > <https://wiki.php.net/rfc/sealed_classes> > > A major concern for few people have been the syntax, in which it > introduces 2 new keywords into the languages, therefor, i have added a > section about alternative syntax which could be used to avoid this > problem. > > Regards, > > Saif.
Hello, And why not using an attribute, such as in HackLang ? Regards,
-- Pierre

Benjamin Eberlei

5 years ago
On Sat, Apr 24, 2021 at 2:56 PM Pierre <pierre-php@processus.org> wrote:
> Le 24/04/2021 à 12:55, Saif Eddin Gmati a écrit : > > Hello Internals, > > > > I'm sending this email to open discussion about sealed classes, > > interfaces, and traits feature for PHP 8.1. > > > > I have create a Draft RFC here: > > https://wiki.php.net/rfc/sealed_classes > > <https://wiki.php.net/rfc/sealed_classes> > > > > A major concern for few people have been the syntax, in which it > > introduces 2 new keywords into the languages, therefor, i have added a > > section about alternative syntax which could be used to avoid this > > problem. > > > > Regards, > > > > Saif. > > Hello, > > And why not using an attribute, such as in HackLang ? >
+1 on this, I said the same on the "never/noreturn" RFC. There is a much less invasive way to add new keywords/flags to functions by using attributes. Imho this decouples new features from the language and reduces the "risk" of adding them to the language. That should increase the likeliness of it getting accepted in my opinion.

Unnamed Person

5 years ago
On Sat, Apr 24, 2021 at 8:04 AM Benjamin Eberlei <kontakt@beberlei.de> wrote:
> > On Sat, Apr 24, 2021 at 2:56 PM Pierre <pierre-php@processus.org> wrote: > > > Le 24/04/2021 à 12:55, Saif Eddin Gmati a écrit : > > > Hello Internals, > > > > > > I'm sending this email to open discussion about sealed classes, > > > interfaces, and traits feature for PHP 8.1. > > > > > > I have create a Draft RFC here: > > > https://wiki.php.net/rfc/sealed_classes > > > <https://wiki.php.net/rfc/sealed_classes> > > > > > > A major concern for few people have been the syntax, in which it > > > introduces 2 new keywords into the languages, therefor, i have added a > > > section about alternative syntax which could be used to avoid this > > > problem. > > > > > > Regards, > > > > > > Saif. > > > > Hello, > > > > And why not using an attribute, such as in HackLang ? > > > > +1 on this, I said the same on the "never/noreturn" RFC. There is a much > less invasive way to add new keywords/flags to functions by using > attributes. > > Imho this decouples new features from the language and reduces the "risk" > of adding them to the language. That should increase the likeliness of it > getting accepted in my opinion.
I think an attribute may be appropriate here because sealed types act like normal types, except we restrict who can extend them. Additionally, we have to provide data about which types can extend the sealed type, so it's not just a simple on/off type behavioral switch (which I think is an antipattern for attributes based on my experience in other languages that have them). This is different from a return type `never`. A function which never returns cannot meaningfully have any return type at all -- using `void` or some other type with an attribute would be a lie. Additionally, there isn't any meta-data to associate with the `never`. I hope this comment doesn't digress into a conversation about `never`; that isn't my point. I'm trying to provide more justification about when I think attributes are appropriate, because I think they may be appropriate here and I think it's useful to show how `never` is different.

Matthew Brown

5 years ago
> On Apr 24, 2021, at 10:43 AM, Levi Morrison via internals <internals@lists.php.net> wrote: > > On Sat, Apr 24, 2021 at 8:04 AM Benjamin Eberlei <kontakt@beberlei.de> wrote: >> >>> On Sat, Apr 24, 2021 at 2:56 PM Pierre <pierre-php@processus.org> wrote: >>> >>> Le 24/04/2021 à 12:55, Saif Eddin Gmati a écrit : >>>> Hello Internals, >>>> >>>> I'm sending this email to open discussion about sealed classes, >>>> interfaces, and traits feature for PHP 8.1. >>>> >>>> I have create a Draft RFC here: >>>> https://wiki.php.net/rfc/sealed_classes >>>> <https://wiki.php.net/rfc/sealed_classes> >>>> >>>> A major concern for few people have been the syntax, in which it >>>> introduces 2 new keywords into the languages, therefor, i have added a >>>> section about alternative syntax which could be used to avoid this >>>> problem. >>>> >>>> Regards, >>>> >>>> Saif. >>> >>> Hello, >>> >>> And why not using an attribute, such as in HackLang ? >>> >> >> +1 on this, I said the same on the "never/noreturn" RFC. There is a much >> less invasive way to add new keywords/flags to functions by using >> attributes. >> >> Imho this decouples new features from the language and reduces the "risk" >> of adding them to the language. That should increase the likeliness of it >> getting accepted in my opinion. > > I think an attribute may be appropriate here because sealed types act > like normal types, except we restrict who can extend them. > Additionally, we have to provide data about which types can extend the > sealed type, so it's not just a simple on/off type behavioral switch > (which I think is an antipattern for attributes based on my experience > in other languages that have them). > > This is different from a return type `never`. A function which never > returns cannot meaningfully have any return type at all -- using > `void` or some other type with an attribute would be a lie. > Additionally, there isn't any meta-data to associate with the `never`. > I hope this comment doesn't digress into a conversation about `never`; > that isn't my point. I'm trying to provide more justification about > when I think attributes are appropriate, because I think they may be > appropriate here and I think it's useful to show how `never` is > different. > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php >
Yeah I second this — I think an attribute might be more appropriate here, and I obviously didn’t feel that way about the “never” RFC. One big benefit of a keyword over an equivalent attribute is that when you see `#[Sealed(...)]` you have to check use statements above to ensure it refers to the actual ‘Sealed’ attribute. What if PHP reserved double-underscore-prefixed attributes for engine use (and treated them as fully-qualified)? Hack does this currently, so you always know what a `<<__Sealed(Foo::class, Bar::class)>>` attribute will do, regardless of use statements. Best wishes, Matt

Saif Eddin Gmati

5 years ago
personally I'm okay with using an attribute, but using a keyword for type system related features seems like a better fit. I have added it to the alternative syntax list, which would have a separate vote. https://wiki.php.net/rfc/sealed_classes#syntax ---- On Sat, 24 Apr 2021 16:24:03 +0100 Matthew Brown <matthewmatthew@gmail.com> wrote ----
> On Apr 24, 2021, at 10:43 AM, Levi Morrison via internals <mailto:internals@lists.php.net> wrote: > > On Sat, Apr 24, 2021 at 8:04 AM Benjamin Eberlei <mailto:kontakt@beberlei.de> wrote: >> >>> On Sat, Apr 24, 2021 at 2:56 PM Pierre <mailto:pierre-php@processus.org> wrote: >>> >>> Le 24/04/2021 à 12:55, Saif Eddin Gmati a écrit : >>>> Hello Internals, >>>> >>>> I'm sending this email to open discussion about sealed classes, >>>> interfaces, and traits feature for PHP 8.1. >>>> >>>> I have create a Draft RFC here: >>>> https://wiki.php.net/rfc/sealed_classes >>>> <https://wiki.php.net/rfc/sealed_classes> >>>> >>>> A major concern for few people have been the syntax, in which it >>>> introduces 2 new keywords into the languages, therefor, i have added a >>>> section about alternative syntax which could be used to avoid this >>>> problem. >>>> >>>> Regards, >>>> >>>> Saif. >>> >>> Hello, >>> >>> And why not using an attribute, such as in HackLang ? >>> >> >> +1 on this, I said the same on the "never/noreturn" RFC. There is a much >> less invasive way to add new keywords/flags to functions by using >> attributes. >> >> Imho this decouples new features from the language and reduces the "risk" >> of adding them to the language. That should increase the likeliness of it >> getting accepted in my opinion. > > I think an attribute may be appropriate here because sealed types act > like normal types, except we restrict who can extend them. > Additionally, we have to provide data about which types can extend the > sealed type, so it's not just a simple on/off type behavioral switch > (which I think is an antipattern for attributes based on my experience > in other languages that have them). > > This is different from a return type `never`. A function which never > returns cannot meaningfully have any return type at all -- using > `void` or some other type with an attribute would be a lie. > Additionally, there isn't any meta-data to associate with the `never`. > I hope this comment doesn't digress into a conversation about `never`; > that isn't my point. I'm trying to provide more justification about > when I think attributes are appropriate, because I think they may be > appropriate here and I think it's useful to show how `never` is > different. > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php >
Yeah I second this — I think an attribute might be more appropriate here, and I obviously didn’t feel that way about the “never” RFC. One big benefit of a keyword over an equivalent attribute is that when you see `#[Sealed(...)]` you have to check use statements above to ensure it refers to the actual ‘Sealed’ attribute. What if PHP reserved double-underscore-prefixed attributes for engine use (and treated them as fully-qualified)? Hack does this currently, so you always know what a `<<__Sealed(Foo::class, Bar::class)>>` attribute will do, regardless of use statements. Best wishes, Matt
-- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php

Saif Eddin Gmati

5 years ago
> personally I'm okay with using an attribute, but using a keyword for type system related features seems like a better fit. > > > > I have added it to the alternative syntax list, which would have a separate vote. > > > > https://wiki.php.net/rfc/sealed_classes#syntax > > > > > > > > > ---- On Sat, 24 Apr 2021 16:24:03 +0100 Matthew Brown <matthewmatthew@gmail.com> wrote ---- > > > > > > On Apr 24, 2021, at 10:43 AM, Levi Morrison via internals <mailto:internals@lists.php.net> wrote: > > > > On Sat, Apr 24, 2021 at 8:04 AM Benjamin Eberlei <mailto:kontakt@beberlei.de> wrote: > >> > >>> On Sat, Apr 24, 2021 at 2:56 PM Pierre <mailto:pierre-php@processus.org> wrote: > >>> > >>> Le 24/04/2021 à 12:55, Saif Eddin Gmati a écrit : > >>>> Hello Internals, > >>>> > >>>> I'm sending this email to open discussion about sealed classes, > >>>> interfaces, and traits feature for PHP 8.1. > >>>> > >>>> I have create a Draft RFC here: > >>>> https://wiki.php.net/rfc/sealed_classes > >>>> <https://wiki.php.net/rfc/sealed_classes> > >>>> > >>>> A major concern for few people have been the syntax, in which it > >>>> introduces 2 new keywords into the languages, therefor, i have added a > >>>> section about alternative syntax which could be used to avoid this > >>>> problem. > >>>> > >>>> Regards, > >>>> > >>>> Saif. > >>> > >>> Hello, > >>> > >>> And why not using an attribute, such as in HackLang ? > >>> > >> > >> +1 on this, I said the same on the "never/noreturn" RFC. There is a much > >> less invasive way to add new keywords/flags to functions by using > >> attributes. > >> > >> Imho this decouples new features from the language and reduces the "risk" > >> of adding them to the language. That should increase the likeliness of it > >> getting accepted in my opinion. > > > > I think an attribute may be appropriate here because sealed types act > > like normal types, except we restrict who can extend them. > > Additionally, we have to provide data about which types can extend the > > sealed type, so it's not just a simple on/off type behavioral switch > > (which I think is an antipattern for attributes based on my experience > > in other languages that have them). > > > > This is different from a return type `never`. A function which never > > returns cannot meaningfully have any return type at all -- using > > `void` or some other type with an attribute would be a lie. > > Additionally, there isn't any meta-data to associate with the `never`. > > I hope this comment doesn't digress into a conversation about `never`; > > that isn't my point. I'm trying to provide more justification about > > when I think attributes are appropriate, because I think they may be > > appropriate here and I think it's useful to show how `never` is > > different. > > > > -- > > PHP Internals - PHP Runtime Development Mailing List > > To unsubscribe, visit: https://www.php.net/unsub.php > > > > Yeah I second this — I think an attribute might be more appropriate here, and I obviously didn’t feel that way about the “never” RFC. > > One big benefit of a keyword over an equivalent attribute is that when you see `#[Sealed(...)]` you have to check use statements above to ensure it refers to the actual ‘Sealed’ attribute. > > What if PHP reserved double-underscore-prefixed attributes for engine use (and treated them as fully-qualified)? Hack does this currently, so you always know what a `<<__Sealed(Foo::class, Bar::class)>>` attribute will do, regardless of use statements. > > Best wishes, > > Matt > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php
Note: i have removed the choice for `#[Sealed(...)]` attribute syntax, while i don't mind it, i don't see a valid point for using attributes, other features ( e.g final classes ) use modifiers, and so should `sealed` as it is more comparable to `final` than other stuff people usually use attributes for ( e.g: #[Assert\Length(min: 2)], #[ORM\Entity], #[ApiResource] .. etc ).

Dan Ackroyd

5 years ago
On Sat, 24 Apr 2021 at 15:03, Benjamin Eberlei <kontakt@beberlei.de> wrote:
> > There is a much less invasive way to add new keywords/flags > to functions by using attributes. > > Imho this decouples new features from the language and reduces the "risk" > of adding them to the language.
I think I disagree with this very strongly, and plan* to vote against any RFC that embeds another language in annotations.** It might be quicker, easier, and more seductive to implement language level features in them, but it is a massive trade-off in making code hard to reason about.
> That should increase the likeliness of it > getting accepted in my opinion.
This can also be pronounced as "makes it more likely to slip bad ideas in core without thinking them through fully". My experience of annotations being used as an embedded language is Java, and it's one of the reasons why I no longer use that language. Not only is code with many lines of annotations hard to read, it results in behaviour that is very hard to reason about. I literally spent 40 hours (spread over the course of 5 weeks), trying to figure out this bug https://stackoverflow.com/q/9072749/778719 . If my colleague hadn't been able to tell me the answer, the only way I could have debugged the problem myself is using a bytecode level debugger, to step through the internal details of what was happening. I'd prefer it if we didn't repeat (what I consider to be) the mistakes of Java. cheers Dan Ack * possibly with the exception of optimization annotations e.g. 'memoize'. ** "From a language design perspective, annotations form a mini-language embedded in Java" - https://blog.softwaremill.com/the-case-against-annotations-4b2fb170ed67 Though probably most of the other links from "java annotations are crap" are also appropriate.

Pierre

5 years ago
Le 27/04/2021 à 15:21, Dan Ackroyd a écrit :
> On Sat, 24 Apr 2021 at 15:03, Benjamin Eberlei <kontakt@beberlei.de> wrote: >> There is a much less invasive way to add new keywords/flags >> to functions by using attributes. >> >> Imho this decouples new features from the language and reduces the "risk" >> of adding them to the language. > I think I disagree with this very strongly, and plan* to vote against > any RFC that embeds another language in annotations.** > > It might be quicker, easier, and more seductive to implement language > level features in them, but it is a massive trade-off in making code > hard to reason about. > >> That should increase the likeliness of it >> getting accepted in my opinion. > This can also be pronounced as "makes it more likely to slip bad ideas > in core without thinking them through fully". > > My experience of annotations being used as an embedded language is > Java, and it's one of the reasons why I no longer use that language. > > Not only is code with many lines of annotations hard to read, it > results in behaviour that is very hard to reason about. I literally > spent 40 hours (spread over the course of 5 weeks), trying to figure > out this bug https://stackoverflow.com/q/9072749/778719 . If my > colleague hadn't been able to tell me the answer, the only way I could > have debugged the problem myself is using a bytecode level debugger, > to step through the internal details of what was happening. > > I'd prefer it if we didn't repeat (what I consider to be) the mistakes of Java. > > cheers > Dan > Ack
Hello, Yes, after reading a few answers I changed my mind and I do agree with you know, this is a language feature and should be in the language syntax and not in attributes. Regards,
-- Pierre

Olle Härstedt

5 years ago
2021-04-24 12:56 GMT, Pierre <pierre-php@processus.org>:
> Le 24/04/2021 à 12:55, Saif Eddin Gmati a écrit : >> Hello Internals, >> >> I'm sending this email to open discussion about sealed classes, >> interfaces, and traits feature for PHP 8.1. >> >> I have create a Draft RFC here: >> https://wiki.php.net/rfc/sealed_classes >> <https://wiki.php.net/rfc/sealed_classes> >> >> A major concern for few people have been the syntax, in which it >> introduces 2 new keywords into the languages, therefor, i have added a >> section about alternative syntax which could be used to avoid this >> problem. >> >> Regards, >> >> Saif.
1) Doesn't this violate the principle: It should be possible to add new features without touching old code? 2) Isn't namespace-internal access a better feature for the same purpose? That is, only allows a class to be extended within the same namespace. Olle

Saif Eddin Gmati

5 years ago
> Doesn't this violate the principle: It should be possible to add new features without touching old code?
This depends on which syntax is picked, both `for` and attribute syntax will be completely BC. using `sealed`+`permits` or `permits` only will result in these keywords being reserved, so classes named `Sealed` or `Permits` will not work in PHP 8.1.
> Isn't namespace-internal access a better feature for the same purpose? That is, only allows a class to be extended within the same namespace.
No, this is a different feature, sealed classes can permit classes in a completely different namespace, and restrict inheritance to a per-defined list of classes. If we would allow inheritance in the same namespace, it's extremely easy to get around it: ``` namespace Lib {   abstract class MyProxy implements PrivateFooInterface {} } namespace App {   class MyFooImplementation extends \Lib\MyProxy {     // ...   } } ``` while it is possible to get around sealed, or even final keywords ( https://github.com/dg/bypass-finals/blob/master/src/BypassFinals.php ), bypassing private classes/interface is much easier, and doesn't require any hacks. ---- On Sat, 24 Apr 2021 16:52:20 +0100 Olle Härstedt <olleharstedt@gmail.com> wrote ---- 2021-04-24 12:56 GMT, Pierre <mailto:pierre-php@processus.org>:
> Le 24/04/2021 à 12:55, Saif Eddin Gmati a écrit : >> Hello Internals, >> >> I'm sending this email to open discussion about sealed classes, >> interfaces, and traits feature for PHP 8.1. >> >> I have create a Draft RFC here: >> https://wiki.php.net/rfc/sealed_classes >> <https://wiki.php.net/rfc/sealed_classes> >> >> A major concern for few people have been the syntax, in which it >> introduces 2 new keywords into the languages, therefor, i have added a >> section about alternative syntax which could be used to avoid this >> problem. >> >> Regards, >> >> Saif.
1) Doesn't this violate the principle: It should be possible to add new features without touching old code? 2) Isn't namespace-internal access a better feature for the same purpose? That is, only allows a class to be extended within the same namespace. Olle
-- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php

Olle Härstedt

5 years ago
2021-04-24 17:59 GMT+02:00, Saif Eddin Gmati <azjezz@void.tn>:
>> Doesn't this violate the principle: It should be possible to add new >> features without touching old code? > > This depends on which syntax is picked, both `for` and attribute syntax will > be completely BC.
I'm not talking about BC, but the maintainability of the new feature itself. For the shape example, you'd need to edit the original file for each new shape you add, which is detrimental for maintainability and scalability. So what's a good use-case? Olle

Marco Pivetta

5 years ago
On Sat, Apr 24, 2021, 21:44 Olle Härstedt <olleharstedt@gmail.com> wrote:
> 2021-04-24 17:59 GMT+02:00, Saif Eddin Gmati <azjezz@void.tn>: > >> Doesn't this violate the principle: It should be possible to add new > >> features without touching old code? > > > > This depends on which syntax is picked, both `for` and attribute syntax > will > > be completely BC. > > I'm not talking about BC, but the maintainability of the new feature > itself. For the shape example, you'd need to edit the original file > for each new shape you add, which is detrimental for maintainability > and scalability. So what's a good use-case? >
The main use-case of sealed types is being able to declare total functions around them.

Olle Härstedt

5 years ago
2021-04-24 21:51 GMT+02:00, Marco Pivetta <ocramius@gmail.com>:
> On Sat, Apr 24, 2021, 21:44 Olle Härstedt <olleharstedt@gmail.com> wrote: > >> 2021-04-24 17:59 GMT+02:00, Saif Eddin Gmati <azjezz@void.tn>: >> >> Doesn't this violate the principle: It should be possible to add new >> >> features without touching old code? >> > >> > This depends on which syntax is picked, both `for` and attribute syntax >> will >> > be completely BC. >> >> I'm not talking about BC, but the maintainability of the new feature >> itself. For the shape example, you'd need to edit the original file >> for each new shape you add, which is detrimental for maintainability >> and scalability. So what's a good use-case? >> > > The main use-case of sealed types is being able to declare total functions > around them.
What is "total function" in your discourse? :) Can you find a more concrete example? Preferably one that's relevant for web site/app development. Shapes is a bit too generic, I think. Olle

Larry Garfield

5 years ago
On Sat, Apr 24, 2021, at 2:55 PM, Olle Härstedt wrote:
> 2021-04-24 21:51 GMT+02:00, Marco Pivetta <ocramius@gmail.com>: > > On Sat, Apr 24, 2021, 21:44 Olle Härstedt <olleharstedt@gmail.com> wrote: > > > >> 2021-04-24 17:59 GMT+02:00, Saif Eddin Gmati <azjezz@void.tn>: > >> >> Doesn't this violate the principle: It should be possible to add new > >> >> features without touching old code? > >> > > >> > This depends on which syntax is picked, both `for` and attribute syntax > >> will > >> > be completely BC. > >> > >> I'm not talking about BC, but the maintainability of the new feature > >> itself. For the shape example, you'd need to edit the original file > >> for each new shape you add, which is detrimental for maintainability > >> and scalability. So what's a good use-case? > >> > > > > The main use-case of sealed types is being able to declare total functions > > around them. > > What is "total function" in your discourse? :) Can you find a more > concrete example? Preferably one that's relevant for web site/app > development. Shapes is a bit too generic, I think. > > Olle
A total function is a function that is defined over the entire domain of its inputs. For example, addition is a total function over integers, because for every possible pair of integers you pass to it there is a logical return value. However, square root is not a total function over integers because there are some integers you pass it for which there is not representable return value. (Negative numbers, unless you get into imaginary numbers which PHP doesn't support.) In those cases, you have to throw an exception or return an error code or similar. For a more typical PHP example, getUser(int $id) is not a total function, unless you have PHP_MAX_INT user objects defined in your database. If you pass an int that does not correspond to a defined user, you now have to deal with "user not found" error handling. getUser() is not a total function. getUsers(array $criteria), however, arguably is, because it's logical and reasonable to map all not-found cases to an empty array/collection, which doesn't require any special error handling. In practice, I think all of the use cases for sealed classes are ADT-esque. As I noted before, combining sealed classes with Nikita's new-in-expressions RFC would allow for this (also using my short-functions RFC for this example, although that's a nice-to-have): sealed class Maybe permits Some, None { public const None = new None(); static public function Some($x) => new Some($x); public function value() => throw new NotFoundException(); public function bind(callable $c) => static::None; } final class None extends Maybe {} final class Some extends Maybe { private $val; private function __construct($x) { $this->val = $x; } public function value() => $this->val; public function bind(callable $c) => new static($c($this->val)); } Now if you have an instance of Maybe, you can be absolutely guaranteed that it's either an instance of Some or of None. It's very similar to the guarantee you get for enumerations, that you will have one of a fixed set of dev-defined values and don't need to worry about any other case. You handle None, you handle Some, and now your function is a total function over its Maybe parameter. There are assorted other cases along those lines. That gets you essentially the same functionality by a different route as what the tagged unions RFC (https://wiki.php.net/rfc/tagged_unions) proposes: enum Maybe { case None { public function bind(callable $f) => $this; } }; case Some(private mixed $value) { public function bind(callable $f): Maybe => $f($this->value); }; public function value(): mixed => $this instanceof None ? throw new Exception() : $this->val; } Or to use another example from the tagged unions RFC: enum Distance { case Kilometers(public int $km); case Miles(public int $miles); } vs: sealed interface Distance permits Kilometers, Miles { ... } class Kilometers implements Distance { public function __construct(public int $km) {} } class Miles implements Distance { public function __construct(public int $miles) {} } In either case, a function can now operate on distance and know that it's dealing with a value in miles OR in kilometers, but it doesn't have to worry about yards, furlongs, or light-years. Combined with a pattern-matching operator (which Ilija is working on here: https://wiki.php.net/rfc/pattern-matching), it would make it possible to combine ADTs/sealed classes with a match() statement and know that you've covered every possible situation with a trivial amount of code. Enums, Sealed classes, and tagged unions all play in the same logical space, of allowing the developer to more precisely define their problem space and data model in a way that "makes invalid states unrepresentable",,and thus eliminates a large amount of error handling resulting in code that is harder if not impossible to "get wrong." I think it's clear that there's desire to have such capability, but the specifics of how we get there are not as clear-cut. For instance, as currently envisioned tagged unions would extend enums, and as they're a dedicated language construct we can build in read-only properties. Sealed classes wouldn't be able to do that... however, if we also added asymmetric visibility to properties or Nikita's proposed property accessors, a class could itself force a property to be public-read-only. At that point, you would be able to implement the entire tagged-union RFC's functionality by combining sealed classes, read-only properties, and pattern matching. It would just have a less specific-to-the-use-case syntax, which could be good or bad depending on your point of view. I hope that clears up the problem space this RFC is working in. Whether we want to achieve that functionality through enum-based tagged unions or through sealed classes, new-in-expression, and read-only properties is an open question, and I'm not entirely sure yet which I favor. I can see pros and cons to both approaches; I just know I really want at least one of them, in 8.1 if at all possible. :-) --Larry Garfield

Olle Härstedt

5 years ago
> A total function is a function that is defined over the entire domain of its > inputs. For example, addition is a total function over integers, because > for every possible pair of integers you pass to it there is a logical return > value. However, square root is not a total function over integers because > there are some integers you pass it for which there is not representable > return value. (Negative numbers, unless you get into imaginary numbers > which PHP doesn't support.) In those cases, you have to throw an exception > or return an error code or similar.
Maybe nitpicking, but PHP-land shouldn't make up their own definitions: "A total function is a function that is defined for all possible values of its input. That is, it terminates and returns a value." https://softwareengineering.stackexchange.com/questions/334874/in-the-context-of-functional-programming-what-are-total-functions-and-partia Which means a total function is guaranteed to not have any errors, like exceptions or division by zero. Compare with languages F* or Koka which support this notation. I get your point tho. :) Olle

Larry Garfield

5 years ago
On Sun, Apr 25, 2021, at 2:04 AM, Olle Härstedt wrote:
> > A total function is a function that is defined over the entire domain of its > > inputs. For example, addition is a total function over integers, because > > for every possible pair of integers you pass to it there is a logical return > > value. However, square root is not a total function over integers because > > there are some integers you pass it for which there is not representable > > return value. (Negative numbers, unless you get into imaginary numbers > > which PHP doesn't support.) In those cases, you have to throw an exception > > or return an error code or similar. > > Maybe nitpicking, but PHP-land shouldn't make up their own > definitions: "A total function is a function that is defined for all > possible values of its input. That is, it terminates and returns a > value." > https://softwareengineering.stackexchange.com/questions/334874/in-the-context-of-functional-programming-what-are-total-functions-and-partia > > Which means a total function is guaranteed to not have any errors, > like exceptions or division by zero. Compare with languages F* or Koka > which support this notation.
That... is literally what I said. There was no making up definitions. I was using the actual mathematical definition. PHP is quite capable of having total functions, they're a good thing, and we should try to encourage them where feasible. --Larry Garfield

Olle Härstedt

5 years ago
> For a more typical PHP example, getUser(int $id) is not a total function, > unless you have PHP_MAX_INT user objects defined in your database. If you > pass an int that does not correspond to a defined user, you now have to deal > with "user not found" error handling. getUser() is not a total function. > getUsers(array $criteria), however, arguably is, because it's logical and > reasonable to map all not-found cases to an empty array/collection, which > doesn't require any special error handling.
getUser() could return a nullable type. But totality assumes purity, and I'm assuming you didn't hard-code all users inside the getUser() function. :) As soon as you interact with the outside world, you can have exception and errors, and thus you can't guarantee termination. In any case, I think the concept of totality is pretty foreign to PHP, and we can probably leave it behind (you'd have to make sure - statically, in the type-system - there are no infinite recursion, no infinite loops, ...). Olle

Olle Härstedt

5 years ago
> In practice, I think all of the use cases for sealed classes are ADT-esque. > As I noted before, combining sealed classes with Nikita's new-in-expressions > RFC would allow for this (also using my short-functions RFC for this > example, although that's a nice-to-have): > > sealed class Maybe permits Some, None { > > public const None = new None(); > > static public function Some($x) => new Some($x); > > public function value() => throw new NotFoundException(); > > public function bind(callable $c) => static::None; > } > > final class None extends Maybe {} > > final class Some extends Maybe { > private $val; > private function __construct($x) { $this->val = $x; } > > public function value() => $this->val; > > public function bind(callable $c) => new static($c($this->val)); > }
Yes, the Maybe/Option type is a good example! Because you know there will never be another extension. But it's worth noting that whenever you do *not* know that, these concepts suffer, even in functional programming, by the same issues as I mentioned before with regard to maintainability - you can't easily extend it without touching old code (there are attempts to fix this by making algebraic datatypes extensible, but it didn't get widely adopted AFAIK). Also see this thread about the expression problem: https://stackoverflow.com/a/871375/2138090 Disregarding the limitations of maintainability, the real power of algebraic datatypes is of course the pattern matching functionality seen in OCaml and Haskell. I'm leaning towards tagged unions + pattern matching have more to offer PHP than sealed classes (and even more so when pattern matching can be extended with guard clauses and catching exceptions). The RFC author(s) might want to extend the RFC to reflect the relation to tagged unions, and how they overlap (or not)? Olle

Saif Eddin Gmati

5 years ago
---- On Sun, 25 Apr 2021 08:39:37 +0100 Olle Härstedt <olleharstedt@gmail.com> wrote ----
> > In practice, I think all of the use cases for sealed classes are ADT-esque. > > As I noted before, combining sealed classes with Nikita's new-in-expressions > > RFC would allow for this (also using my short-functions RFC for this > > example, although that's a nice-to-have): > > > > sealed class Maybe permits Some, None { > > > > public const None = new None(); > > > > static public function Some($x) => new Some($x); > > > > public function value() => throw new NotFoundException(); > > > > public function bind(callable $c) => static::None; > > } > > > > final class None extends Maybe {} > > > > final class Some extends Maybe { > > private $val; > > private function __construct($x) { $this->val = $x; } > > > > public function value() => $this->val; > > > > public function bind(callable $c) => new static($c($this->val)); > > } > > Yes, the Maybe/Option type is a good example! Because you know there > will never be another extension. But it's worth noting that whenever > you do *not* know that, these concepts suffer, even in functional > programming, by the same issues as I mentioned before with regard to > maintainability - you can't easily extend it without touching old code > (there are attempts to fix this by making algebraic datatypes > extensible, but it didn't get widely adopted AFAIK). Also see this > thread about the expression problem: > https://stackoverflow.com/a/871375/2138090 > > Disregarding the limitations of maintainability, the real power of > algebraic datatypes is of course the pattern matching functionality > seen in OCaml and Haskell. I'm leaning towards tagged unions + pattern > matching have more to offer PHP than sealed classes (and even more so > when pattern matching can be extended with guard clauses and catching > exceptions). The RFC author(s) might want to extend the RFC to reflect > the relation to tagged unions, and how they overlap (or not)? > > Olle > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php > >
As mentioned in a previous email ( https://news-web.php.net/php.internals/114134 ), there's many differences between ADTs and Sealed classes, and in my opinion that's enough to have them both in the language as personally i have use cases where ADTs won't work. examples: - `sealed class UnionType permits ArrayKeyType, NumType` ( https://github.com/azjezz/psl/tree/1.7.x/src/Psl/Type/Internal ) - `sealed interface ExceptionInterface permits Exception` ( https://github.com/azjezz/psl/blob/1.7.x/src/Psl/Type/Exception ) - applies to all components in library - `sealed class Exception permits AssertException, CoercionException` ( https://github.com/azjezz/psl/blob/1.7.x/src/Psl/Type/Exception ) - `sealed class ResourceHandle permits Psl\Filesystem\Internal\ResourceFileHandle` ( https://github.com/azjezz/psl/blob/asio/src/Psl/IO/Internal / https://github.com/azjezz/psl/tree/asio/src/Psl/Filesystem/Internal ) This is just a list of top of my head, the protection `sealed` offers is at the same level of `final`, expect it allow me to use inheritance internally without allowing the end users to do so.

Mike Schinkel

5 years ago
> On Apr 24, 2021, at 11:47 PM, Larry Garfield <larry@garfieldtech.com> wrote: > > On Sat, Apr 24, 2021, at 2:55 PM, Olle Härstedt wrote: >> 2021-04-24 21:51 GMT+02:00, Marco Pivetta <ocramius@gmail.com>: >>> On Sat, Apr 24, 2021, 21:44 Olle Härstedt <olleharstedt@gmail.com> wrote: >>> >>>> 2021-04-24 17:59 GMT+02:00, Saif Eddin Gmati <azjezz@void.tn>: >>>>>> Doesn't this violate the principle: It should be possible to add new >>>>>> features without touching old code? >>>>> >>>>> This depends on which syntax is picked, both `for` and attribute syntax >>>> will >>>>> be completely BC. >>>> >>>> I'm not talking about BC, but the maintainability of the new feature >>>> itself. For the shape example, you'd need to edit the original file >>>> for each new shape you add, which is detrimental for maintainability >>>> and scalability. So what's a good use-case? >>>> >>> >>> The main use-case of sealed types is being able to declare total functions >>> around them. >> >> What is "total function" in your discourse? :) Can you find a more >> concrete example? Preferably one that's relevant for web site/app >> development. Shapes is a bit too generic, I think. >> >> Olle > > A total function is a function that is defined over the entire domain of its inputs. For example, addition is a total function over integers, because for every possible pair of integers you pass to it there is a logical return value. However, square root is not a total function over integers because there are some integers you pass it for which there is not representable return value. (Negative numbers, unless you get into imaginary numbers which PHP doesn't support.) In those cases, you have to throw an exception or return an error code or similar. > > For a more typical PHP example, getUser(int $id) is not a total function, unless you have PHP_MAX_INT user objects defined in your database. If you pass an int that does not correspond to a defined user, you now have to deal with "user not found" error handling. getUser() is not a total function. getUsers(array $criteria), however, arguably is, because it's logical and reasonable to map all not-found cases to an empty array/collection, which doesn't require any special error handling. > > In practice, I think all of the use cases for sealed classes are ADT-esque. As I noted before, combining sealed classes with Nikita's new-in-expressions RFC would allow for this (also using my short-functions RFC for this example, although that's a nice-to-have): > > sealed class Maybe permits Some, None { > > public const None = new None(); > > static public function Some($x) => new Some($x); > > public function value() => throw new NotFoundException(); > > public function bind(callable $c) => static::None; > } > > final class None extends Maybe {} > > final class Some extends Maybe { > private $val; > private function __construct($x) { $this->val = $x; } > > public function value() => $this->val; > > public function bind(callable $c) => new static($c($this->val)); > } > > Now if you have an instance of Maybe, you can be absolutely guaranteed that it's either an instance of Some or of None. It's very similar to the guarantee you get for enumerations, that you will have one of a fixed set of dev-defined values and don't need to worry about any other case. You handle None, you handle Some, and now your function is a total function over its Maybe parameter. > > There are assorted other cases along those lines. > > That gets you essentially the same functionality by a different route as what the tagged unions RFC (https://wiki.php.net/rfc/tagged_unions) proposes: > > enum Maybe { > case None { > public function bind(callable $f) => $this; > } > }; > > case Some(private mixed $value) { > public function bind(callable $f): Maybe => $f($this->value); > }; > > public function value(): mixed => $this instanceof None > ? throw new Exception() > : $this->val; > } > > Or to use another example from the tagged unions RFC: > > enum Distance { > case Kilometers(public int $km); > case Miles(public int $miles); > } > > vs: > > sealed interface Distance permits Kilometers, Miles { ... } > > class Kilometers implements Distance { > public function __construct(public int $km) {} > } > > class Miles implements Distance { > public function __construct(public int $miles) {} > } > > In either case, a function can now operate on distance and know that it's dealing with a value in miles OR in kilometers, but it doesn't have to worry about yards, furlongs, or light-years. Combined with a pattern-matching operator (which Ilija is working on here: https://wiki.php.net/rfc/pattern-matching), it would make it possible to combine ADTs/sealed classes with a match() statement and know that you've covered every possible situation with a trivial amount of code. > > Enums, Sealed classes, and tagged unions all play in the same logical space, of allowing the developer to more precisely define their problem space and data model in a way that "makes invalid states unrepresentable",,and thus eliminates a large amount of error handling resulting in code that is harder if not impossible to "get wrong."
Nothing I am about to write is meant to question the value of total functions, but can you speak to the ramifications of a developer using a library with a total function that returns miles or kilometers in (say) version 2.0, along with the assumed guarantees of said function, but then in version 3.0 the developer adds furlongs as a unit of measure? Yes going from 2.0 to 3.0 is a breaking change per semver, but it doesn't feel like it had to be had the application developer not made the total function assumption. -Mike

Larry Garfield

5 years ago
On Sun, Apr 25, 2021, at 9:48 AM, Mike Schinkel wrote:
> > A total function is a function that is defined over the entire domain of its inputs. For example, addition is a total function over integers, because for every possible pair of integers you pass to it there is a logical return value. However, square root is not a total function over integers because there are some integers you pass it for which there is not representable return value. (Negative numbers, unless you get into imaginary numbers which PHP doesn't support.) In those cases, you have to throw an exception or return an error code or similar. > > > > For a more typical PHP example, getUser(int $id) is not a total function, unless you have PHP_MAX_INT user objects defined in your database. If you pass an int that does not correspond to a defined user, you now have to deal with "user not found" error handling. getUser() is not a total function. getUsers(array $criteria), however, arguably is, because it's logical and reasonable to map all not-found cases to an empty array/collection, which doesn't require any special error handling.
> Nothing I am about to write is meant to question the value of total > functions, but can you speak to the ramifications of a developer using > a library with a total function that returns miles or kilometers in > (say) version 2.0, along with the assumed guarantees of said function, > but then in version 3.0 the developer adds furlongs as a unit of > measure? > > Yes going from 2.0 to 3.0 is a breaking change per semver, but it > doesn't feel like it had to be had the application developer not made > the total function assumption. > > -Mike
It would be a breaking change, because you're changing the definition of the problem space. Just as if you added a new option to an enum, or changed an Interface to have a method return iterable instead of array. If you change the definition of the data model, that's a breaking change and all bets are off anyway. Whether or not making it sealed in the first place was a good call is going to vary widely depending on the situation. They're really not appropriate for service objects, where by design you could have an infinite number of possible PaymentGateway objects. They're much more applicable for data objects that represent a finite number of business-relevant situations, where changing them means the problem space is changing *anyway* so changing code around the problem space is going to happen anyway. --Larry Garfield

Christian Schneider

5 years ago
Am 25.04.2021 um 05:47 schrieb Larry Garfield <larry@garfieldtech.com>:
> In practice, I think all of the use cases for sealed classes are ADT-esque. As I noted before, combining sealed classes with Nikita's new-in-expressions RFC would allow for this (also using my short-functions RFC for this example, although that's a nice-to-have): > > sealed class Maybe permits Some, None {
...
> } > > final class None extends Maybe {}
This is exactly the thing I'm worried about. Say I want to add something like logging to the None type. Now your sealed and final classes prevent me from defining MyNone extending None even though it would be 100% compatible with None. Just because *you* deemed that useless or wrong. I've encountered situations like this and came to the conclusion that while this makes sense for languages like Haskell - where the whole idea is to be able to reason about a complex type system - it is an anti-pattern for other languages like PHP. Referring to another post, not yours: People, please don't use Java as a reason to add something to PHP, Java is the king of anti-patterns ;-) - Chris

David Gebler

5 years ago
Yes I agree Chris, this is the same kind of argument I am making.
> Note that the *exact* same argument could be made for typing parameters.
I can document via a docblock that I expect a given parameter to be a string, or a Request object, or whatever. "There is little to no benefit in expressing that through a new language construct rather than through existing constructs and design patterns." Yes and no. There is a critical difference between type checking and sealed - type checking can actually prevent and catch bugs in the form of objective logical errors (e.g. $total = getSum(1, 2, "not a number")) before they occur, whereas sealed can only prevent things you did not intend or foresee, without knowing anything about how I am using some code; there is no bug or logical error which the use of sealed will prevent *in and of itself*.
> Except there very much is a benefit, for making the intent of code
clearer and for allowing the engine to syntactically make certain invalid states impossible. This is not runtime benefit, it is runtime overhead. PHP cannot generate a better opcode sequence for knowing a class is sealed. In terms of implementation, the only thing it can do is scream "Hey! The author of this class didn't intend for you to use it this way!" - your IDE can do that from an attribute. And although I would say the claim no one will ever have a legitimate & good reason to extend some class and be able to do so in a way which does not violate whatever protections against invalid state you had in mind is a very bold prediction which is rarely borne out by reality - my objection here is not about people marking classes as sealed, it's that we don't need a new language construct and keyword to achieve the only benefit it delivers (declaration of intent to users and IDE). If PHP reasoned about classes in the same way compiled, statically typed languages do and therefore derived some tangible compile-time benefit from a sealed construct, I would be in favour of it. On Mon, 26 Apr 2021, 08:54 Christian Schneider, <cschneid@cschneid.com> wrote:

Ilija Tovilo

5 years ago
Hi Christian On Mon, Apr 26, 2021 at 9:54 AM Christian Schneider <cschneid@cschneid.com> wrote:
> > Am 25.04.2021 um 05:47 schrieb Larry Garfield <larry@garfieldtech.com>: > > In practice, I think all of the use cases for sealed classes are ADT-esque. As I noted before, combining sealed classes with Nikita's new-in-expressions RFC would allow for this (also using my short-functions RFC for this example, although that's a nice-to-have): > > > > sealed class Maybe permits Some, None { > > ... > > > } > > > > final class None extends Maybe {} > > > > This is exactly the thing I'm worried about. > > Say I want to add something like logging to the None type. > Now your sealed and final classes prevent me from defining MyNone extending None even though it would be 100% compatible with None. Just because *you* deemed that useless or wrong.
The point of sealed type is to fix the number of subclasses a given type can have, which means you can handle a value by type (as that list is not finite). Code that handles Optional values could look like this: ``` if ($option instanceof Option\None) { throw new Exception(); } // We now know the value is a Some var_dump($option->value); ``` If you suddenly provide your own version of None the code above will break. This is probably more obvious when you look at the enum-equivalent. ``` enum Option { case None; case Some($value); } ``` To most people it's obvious that you can't add new cases to an existing enum. It doesn't sound sensible to add logging to a data class. That's something that belongs into a hook or service of some kind. People might also want to use sealed for behavioral classes, the use case here is the same as final. Final is not here to make your life harder. It's here to make the lives of the library maintainers easier. If they have to reason about every way a method could be overridden, every change in the library would become more risky, require more thought and more frequent major version updates. This means more work and less features for you, too. Ilija

Guilliam Xavier

5 years ago
Hi, On Mon, Apr 26, 2021 at 9:54 AM Christian Schneider <cschneid@cschneid.com> wrote:
> Am 25.04.2021 um 05:47 schrieb Larry Garfield <larry@garfieldtech.com>: > ... > > sealed class Maybe permits Some, None { > ... > > } > > > > final class None extends Maybe {} > > This is exactly the thing I'm worried about. > > Say I want to add something like logging to the None type. > Now your sealed and final classes prevent me from defining MyNone > extending None even though it would be 100% compatible with None. >
I just want to note that this has nothing to do with Maybe made sealed (which seems legit), only with None made final (which... could be debated, but unrelated to the RFC at hand). Regards,
-- Guilliam Xavier

Christian Schneider

5 years ago
Am 24.04.2021 um 21:51 schrieb Marco Pivetta <ocramius@gmail.com>:
> On Sat, Apr 24, 2021, 21:44 Olle Härstedt <olleharstedt@gmail.com <mailto:olleharstedt@gmail.com>> wrote: > >> 2021-04-24 17:59 GMT+02:00, Saif Eddin Gmati <azjezz@void.tn>: >>>> Doesn't this violate the principle: It should be possible to add new >>>> features without touching old code? >>> >>> This depends on which syntax is picked, both `for` and attribute syntax >> will >>> be completely BC. >> >> I'm not talking about BC, but the maintainability of the new feature >> itself. For the shape example, you'd need to edit the original file >> for each new shape you add, which is detrimental for maintainability >> and scalability. So what's a good use-case?
I'm with Olle here: This sounds like an anti-pattern to me. The example could not be worse: Why should I not be allowed to add a hexagon shape?
> The main use-case of sealed types is being able to declare total functions > around them.
Could you elaborate on what's the real-world use-case for your main use-case? This sounds like another case of a feature based in (math) theory which leads to artificially locked down code. - Chris

David Gebler

5 years ago
I don't love this idea, I'm not very fond of the final keyword, either; I've always believed annotations (or attributes in PHP these days) are a better of way of indicating you, as an author of a class, did not write it with inheritability in mind or intended than restricting language features through syntactic constructs. The RFC says "when you have a class in your code base that shares some implementation detail between 2 or more other objects, your only protection against others making use of this class is to add `@internal` annotation, which doesn't offer any runtime guarantee that no one is extending this object", to which I ask - why do you need this guarantee? What does it qualitatively add? If I make a judgement that I want to extend your class or implement your interface, I can just delete the sealed keyword from your code and carry on. So it doesn't actually offer any guarantee at all that I'm not extending the type. The best it can achieve is to indicate your intentions, which I believe can be adequately done today through an attribute, no addition to the language needed. On Sat, Apr 24, 2021 at 9:01 PM Christian Schneider <cschneid@cschneid.com> wrote:

Mike Schinkel

5 years ago
> On Apr 24, 2021, at 7:39 PM, David Gebler <davidgebler@gmail.com> wrote: > > I don't love this idea, I'm not very fond of the final keyword, either;
I'll start by saying the final keyword caused me a tremendous amount of heartache because it was used on a class in a framework that I badly, badly needed to extend. But even so, I recognize why they used it, and I still don't have a great argument for how they could address the reasons they used it some other way.
> I've always believed annotations (or attributes in PHP these days) are a > better of way of indicating you, as an author of a class, did not write it > with inheritability in mind or intended than restricting language features > through syntactic constructs. > > The RFC says "when you have a class in your code base that shares some > implementation detail between 2 or more other objects, your only protection > against others making use of this class is to add `@internal` annotation, > which doesn't offer any runtime guarantee that no one is extending this > object", to which I ask - why do you need this guarantee? What does it > qualitatively add? If I make a judgement that I want to extend your class > or implement your interface, I can just delete the sealed keyword from your > code and carry on. So it doesn't actually offer any guarantee at all that > I'm not extending the type.
Actually, it does offer such a guarantee. It guarantees if you are using a non-forked version of the original developer's (OD's) library or framework then that class won't be extended. When someone pulls the original non-forked version from its source repository — such as when using Composer — then that code will be (effectively) guaranteed not to be extended. OTOH, if you do delete the sealed (or final) keyword you have then forked the code, in a defacto manner if not a literal one. If you use a forked version of the code, you now own the maintenance of that code and any bugs that are generated by your forked changes in using code. The original developer has no moral, ethical or even contractual obligation to care about the breakage you cause. Hypothetical example: You fork the code, remove sealed/final, then subclass the code and add a method, let's call it ToString(). And you write your application to use ToString(). Now the OD releases a new minor version and they also add a ToString() method. Applications using your fork probably cannot use the new version of the OD's library because when the library calls ToString() your version is called. So you have to update your application to use the new version of the library and once again remove sealed/final. AND, if your code is instead another add-on library, now users of your add-on library will also have to fix their code too. Which could potentially be a large number of users if your add-on is successful. So not using final or sealed can result in some really hairy and possibly impossible to fully resolve backward compatibility concerns for developers who publish libraries and/or frameworks.
> The best it can achieve is to indicate your > intentions, which I believe can be adequately done today through an > attribute, no addition to the language needed.
Still, I concur with your concerns. Developers too often implement final classes in libraries and frameworks without fully addressing all the use-cases and/or adding enough extensibility points because it makes their lives easier. Because of that final — and sealed, if added — can make the life of an application developer a living hell. So what's the answer? I don't know that I have the ultimate answer, but I would be a lot more comfortable with adding features to PHP such as ones like sealed that restrict the "O" in S.O.L.I.D.[0] if PHP were to offer the following three (3) things, all of which can be found in Go, and I am sure other languages: 1. Class embedding[1] — Allows one class to embed another and immediately have access to all its properties and methods, and also to be able to extract an instance of that embedded class. It is called "Type embedding" in Go. 2.Type definitions[2] — A typedef would allow developers to define constrained versions of existing types, such as `FiveStarRating` which could only contain 1, 2, 3, 4 or 5, or types that identify a signature, for example as `ConvertToString` which could require a closure that implements `func(mixed):string`. In Go you can compose other types to create new types, but I'm not sure if those other type of types could apply to PHP, at least as it currently exists, and especially because it is not a compiled language. 3. Structural typing[3] — Basically interfaces that can be implemented implicitly rather than explicitly. For example, if I wanted to implement a Stringable interface that requires a ToString():string method then structural typing would allow me to implement that interface simply by adding a ToString() method instead of requiring me to also add "implements Stringable" to the class definition. Those three features are all killer language features and would make great additions to PHP. IMO, of course. #fwiw -Mike [0] https://stackify.com/solid-design-open-closed-principle/ [1] https://travix.io/type-embedding-in-go-ba40dd4264df [2] https://go101.org/article/type-system-overview.html [3] https://blog.carbonfive.com/structural-typing-compile-time-duck-typing/ <https://blog.carbonfive.com/structural-typing-compile-time-duck-typing/>

David Gebler

5 years ago
Still, all these problems are solved to the same degree if you add a #[Sealed] attribute to a class which has no functional impact. You have sufficiently indicated to any user that extending this class is not a designed feature and may cause backwards-incompatible breaks with future releases - in a way that both a programmer and IDE can reason about, which in PHP's context is what matters. Attributes arguably even have a greater qualitative advantage that they can be applied right down as far as individual method parameters. In Java the idea of final and sealed classes makes more sense, since we actually to some extent need the compiler to be able to reason about these types and can gain optimization and code generation benefits from its being able to do so. PHP's concept of typing and implementation of type checking as an interpreted language is completely different. I wonder, if final and sealed as language constructs really offer the guarantees about intent and safety their advocates say they do, why are they not the default? Why can no one point me to a language where I have to write something like extendable class Foo permits all { .... } (and there are people who would be in favour of making inheritability this explicit, but I'm not one of them) It's one thing as an author of code to say "I only intended and support this finite set of use-cases", it's quite another to say "and you should be impeded from proceeding with any legitimate use-case I didn't imagine or foresee" In practice, the only thing I've ever seen this achieve is to create difficulties, while the claimed benefits can be adequately (and better) achieved through existing patterns like annotations, interfaces and DI. On Sun, Apr 25, 2021 at 4:36 PM Mike Schinkel <mike@newclarity.net> wrote:

Chase Peeler

5 years ago
On Sun, Apr 25, 2021 at 11:36 AM Mike Schinkel <mike@newclarity.net> wrote:
> > > > On Apr 24, 2021, at 7:39 PM, David Gebler <davidgebler@gmail.com> wrote: > > > > I don't love this idea, I'm not very fond of the final keyword, either; > > I'll start by saying the final keyword caused me a tremendous amount of > heartache because it was used on a class in a framework that I badly, badly > needed to extend. > > But even so, I recognize why they used it, and I still don't have a great > argument for how they could address the reasons they used it some other way. > > > I've always believed annotations (or attributes in PHP these days) are a > > better of way of indicating you, as an author of a class, did not write > it > > with inheritability in mind or intended than restricting language > features > > through syntactic constructs. > > > > The RFC says "when you have a class in your code base that shares some > > implementation detail between 2 or more other objects, your only > protection > > against others making use of this class is to add `@internal` annotation, > > which doesn't offer any runtime guarantee that no one is extending this > > object", to which I ask - why do you need this guarantee? What does it > > qualitatively add? If I make a judgement that I want to extend your class > > or implement your interface, I can just delete the sealed keyword from > your > > code and carry on. So it doesn't actually offer any guarantee at all that > > I'm not extending the type. > > Actually, it does offer such a guarantee. It guarantees if you are using > a non-forked version of the original developer's (OD's) library or > framework then that class won't be extended. When someone pulls the > original non-forked version from its source repository — such as when using > Composer — then that code will be (effectively) guaranteed not to be > extended. > > OTOH, if you do delete the sealed (or final) keyword you have then forked > the code, in a defacto manner if not a literal one. If you use a forked > version of the code, you now own the maintenance of that code and any bugs > that are generated by your forked changes in using code. The original > developer has no moral, ethical or even contractual obligation to care > about the breakage you cause. >
I'd argue that if the original developer made clear that you shouldn't extend a class, then they still have no moral, ethical, or even contractual obligation to care about the fact you've used the object in a way they were clear was not supported. I'm with David on this one. I can understand the need to enforce a final/sealed concept for core functionality implemented in C which might do some funny things under the hood. I don't think that should be extended to userland. If you want to warn someone, that's fine. But don't totally prohibit them. Given the ability for composer to pull from forked repos and the easy of keeping a forked repo in sync with it's upstream version, creating a fork just to remove a sealed/final designation isn't that difficult to do.
> > Hypothetical example: You fork the code, remove sealed/final, then > subclass the code and add a method, let's call it ToString(). And you write > your application to use ToString(). Now the OD releases a new minor version > and they also add a ToString() method. Applications using your fork > probably cannot use the new version of the OD's library because when the > library calls ToString() your version is called. So you have to update your > application to use the new version of the library and once again remove > sealed/final. > > AND, if your code is instead another add-on library, now users of your > add-on library will also have to fix their code too. Which could > potentially be a large number of users if your add-on is successful. > > So not using final or sealed can result in some really hairy and possibly > impossible to fully resolve backward compatibility concerns for developers > who publish libraries and/or frameworks. > > > The best it can achieve is to indicate your > > intentions, which I believe can be adequately done today through an > > attribute, no addition to the language needed. > > Still, I concur with your concerns. Developers too often implement final > classes in libraries and frameworks without fully addressing all the > use-cases and/or adding enough extensibility points because it makes their > lives easier. Because of that final — and sealed, if added — can make the > life of an application developer a living hell. > > So what's the answer? I don't know that I have the ultimate answer, but I > would be a lot more comfortable with adding features to PHP such as ones > like sealed that restrict the "O" in S.O.L.I.D.[0] if PHP were to offer the > following three (3) things, all of which can be found in Go, and I am sure > other languages: > > 1. Class embedding[1] — Allows one class to embed another and immediately > have access to all its properties and methods, and also to be able to > extract an instance of that embedded class. It is called "Type embedding" > in Go. > > 2.Type definitions[2] — A typedef would allow developers to define > constrained versions of existing types, such as `FiveStarRating` which > could only contain 1, 2, 3, 4 or 5, or types that identify a signature, for > example as `ConvertToString` which could require a closure that implements > `func(mixed):string`. In Go you can compose other types to create new > types, but I'm not sure if those other type of types could apply to PHP, at > least as it currently exists, and especially because it is not a compiled > language. > > 3. Structural typing[3] — Basically interfaces that can be implemented > implicitly rather than explicitly. For example, if I wanted to implement a > Stringable interface that requires a ToString():string method then > structural typing would allow me to implement that interface simply by > adding a ToString() method instead of requiring me to also add "implements > Stringable" to the class definition. > > Those three features are all killer language features and would make great > additions to PHP. IMO, of course. > > #fwiw > > -Mike > > [0] https://stackify.com/solid-design-open-closed-principle/ > [1] https://travix.io/type-embedding-in-go-ba40dd4264df > [2] https://go101.org/article/type-system-overview.html > [3] > https://blog.carbonfive.com/structural-typing-compile-time-duck-typing/ < > https://blog.carbonfive.com/structural-typing-compile-time-duck-typing/>
-- Chase Peeler chasepeeler@gmail.com

Guilliam Xavier

5 years ago
On Sat, Apr 24, 2021 at 12:55 PM Saif Eddin Gmati <azjezz@protonmail.com> wrote:
> Hello Internals, > > I'm sending this email to open discussion about sealed classes, > interfaces, and traits feature for PHP 8.1. > > I have create a Draft RFC here: https://wiki.php.net/rfc/sealed_classes > > A major concern for few people have been the syntax, in which it > introduces 2 new keywords into the languages, therefor, i have added a > section about alternative syntax which could be used to avoid this problem. > > Regards, > > Saif. >
Hello, To me the first sentence of the RFC is debatable:
> The purpose of inheritance is code reuse, for when you have a class that
shares common functionality, and you want others to be able to extend it and make use of this functionality in their own class. That sounds like [abstract] base classes, which certainly permit that, but I wouldn't state that "the purpose" of [designing] class hierarchies is "code reuse", which can also (better?) be achieved with traits or even simply composition (by the way, the introduction then mentions that "PHP has the `Throwable` interface, which defines common functionality between `Error` and `Exception` and is implemented by both", but there is no "code reuse" in an interface). I also agree with others that Shape is probably not a good example, and the ResultInterface example feels like an enum/ADT (and the FilesystemTrait example I guess is to replace `@internal` phpDoc). I'm not saying that this RFC is bad, but probably not as convincing as it could be. Regards,
-- Guilliam Xavier