> On Apr 24, 2021, at 7:39 PM, David Gebler <davidgeb...@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/>

Reply via email to