On Thu, Mar 20, 2025, 18:58 Rob Landers <rob@bottled.codes> wrote:

>
>
> On Thu, Mar 20, 2025, at 16:41, Bob Weinand wrote:
>
>
> Am 20.03.2025 um 16:28 schrieb Rob Landers <rob@bottled.codes>:
>
> 
>
>
> On Thu, Mar 20, 2025, at 16:12, Bob Weinand wrote:
>
>
> Am 20.03.2025 um 15:51 schrieb Rob Landers <rob@bottled.codes>:
>
> 
>
>
> On Wed, Mar 19, 2025, at 21:09, Bob Weinand wrote:
>
>
> On 19.3.2025 16:04:06, Rob Landers wrote:
>
> On Tue, Mar 18, 2025, at 03:37, Bob Weinand wrote:
>
> Okay, I see the point with LSP. I'm not sure whether we need to preserve
> LSP for that specific scenario, but neither can I say that we should ignore
> it.
>
> (Effectively implementing LSP would mean that there's an implicit
> interface matching all public method signatures of the parent class, for
> child classes - which is doable, but possibly too much for the initial RFC.)
> I would however ask, should we not implement LSP compatible inner classes,
> to enforce that no child class may name a class the same than any
> non-private inner class declared by any of its parents, until we resolve
> this question (in possibly a future version of PHP).
> I do not think we should bar ourselves from allowing this in the future.
>
>
> I'm not sure I understand what you are asking. But I think you are saying
> the following should be illegal?
>
> class ParentOuter {
>   class ParentInner {}
> }
>
> class ChildOuter extends ParentOuter {
>   class ParentInner {} // not allowed
> }
>
> Precisely.
>
> And not pretending starts with using a different symbol than a backslash.
>
>
> I have been thinking about this for a couple of days now... When thinking
> through the ramifications of my decision to use :> over ::, this will also
> affect generics, most likely -- whenever that happens. This is because if
> this RFC passes, generics will want to be consistent with whatever exists
> currently.
>
> If we were to use :>, then generics would probably look something like
> this to be consistent:
>
> class Box<T> {
>   public function add(self:>T $item) {}
> }
>
> The same thing would also probably apply to ::
>
> class Box<T> {
>   public function add(self::T $item) {}
> }
>
> With `\`, it nearly follows exactly what you would expect-ish:
>
> use \Box\T as T;
>
> class Box<T> {
>   public function add(T $item) {}
>
> // or without use
>   public function add(Box\T $item) {}
> }
>
> With `\`, we can also just automatically check the current class as part
> of namespace resolution when compiling:
>
> class Box<T> {
>   public function add(T $item) {}
> }
>
> This would also make it easier to user inner classes:
>
> class Outer {
>   public class Inner {}
>   public function foo(Inner $bar) {}
> }
>
> The other syntax options do not allow this; at least, I don't think so.
> I'm very heavily leaning towards `\` as the separator.
>
> — Rob
>
> I'm failing to understand why you'd think this would be related at all?
>
> If we get generics,
> class Box<T> {
>   public function add(T $item) {}
> }
>
> would just work, without any use or such. There will not be a symbol
> Box::T or Box\T, just all mentions of T within the Box class will be taken
> as "this is the generic placeholder" and the compiler takes care.
> It's not like that T will be directly accessible from outside of the class
> or actually a proper type, unlike inner classes.
>
> A generic is not an inner class nor will it look like it. Also, there's no
> accessing of a parents generic - you write class Child<T> extends
> ParentClass<T> - or something along these lines, getting the T available
> for your class.
>
> Bob
>
>
> Yes, that is the question right? It might not affect anything there, but
> there would probably be an argument to keep it consistent with inner
> classes. In PHP, a class is a type; thus an inner class is an inner type,
> and generic types are also an inner type that only exist in the scope of
> their enclosing class, just like private inner classes.
>
> If my logic is incorrect, let me know.
>
> — Rob
>
>
> The difference is that inner classes are a backed type; there's actually a
> class/interface/enum/... behind it.
>
> Generic placeholders are placeholders for another type, which is strictly
> local to the compilation of the class.
>
> Bob
>
>
> If that were the case, then this would be an error:
>
> function add(T $item) {
>   if ($item instanceof T) {}
> }
>
> because T isn't a backed type. I don't think they are the same thing, but
> during runtime, T is most definitely a backed-type; not an empty box. You
> still have to refer to T in some way, and I'm of the opinion that self:>T
> is not ideal (I think we can probably all agree on that?). Even in inner
> classes, it seems to make more sense to just treat it like you would any
> other type in the current namespace.
>
>
> It's a *placeholder* for a backed type.
> At runtime (or during monomorphization), the T will be replaced by the
> actual type.
>
> Whatever name T refers to is the actual backed type.
>
> Bob
>
>
> Right, my point is that you have to refer to T in the first place. If we
> use `\`, then T as-is is fine, because there is a clear resolution to what
> T you are referring to. If we use :> or ::, then when you write Box<T>, are
> you referring to T, as in the generic T; or T as in the class T in the same
> namespace. This is clear when we use "T" literally, but quickly becomes not
> so clear when using something else:
>
> class Item {}
>
> class Box<Item> {
>   public function add(Item $item) {}
> }
>
> This is exactly why I said this matters! In the case above with the
> current :> syntax, we should probably use:
>
> class Box<Item> {
>   public function add(self:>Item $item) {
> }
>
> to explicitly define that we want the inner type "Item." To use the first
> example, you have to define inner types to allow that, but we are currently
> defining inner types in this RFC. I'd personally rather use the first
> example when it comes to generics, which means we need to define that now.
> In other words, I think you are on team "\" without realizing you are on
> team "\".
>
> — Rob
>


Hello all, amazing effort as always.

My 2¢:

RL usecase I immediately would adopt this for - is building IDE-supported
complex DTOs. This means autocompletion, refactor rename, inspections for
undefined keys, etc for the price of using no language ”hacks”.

A real life example - json response from UPS shipping API - it has 200++
keys in up to 10+ levels! The structure keeps changing too, albeit in
glacial speeds, but having it in a structured definition allows one to not
loose ones head when providing continuous support.

However: there's a *bunch* of multilevel ”leaf nodes” which are reused
dozends of times, for example MonetaryAmount - it has the numeric value,
but also a Currency as a property.

If extending a class does not inherit the public/protected nested classes,
we are forced to define the structure of MonetaryAmount in the ”regular”
fashion as separate classes.

Not to mention this non-inheritance is counter-inuitive IMHO.

Also, IMHO ”nested classes” is a better name.

Good luck!

>

Reply via email to