Hello,

I'm now deep into the rabbit hole of trying out "\" as the separator. It does 
give some nice ergonomics when using the nested/inner classes, and I really 
like it much better than ":>", but the amount of technical debt I have run into 
is enormous. For now, my implementation will probably be 'hackish' just to get 
it working (even though I still have to address the technical debt). However, 
it looks like I may have to make namespaces first-class logical spaces in the 
engine (instead of string prefixes) to do it properly. I will probably just 
share my "hackish" solution for now. It is simple enough to follow, but I 
promise you will cringe at the number of string operations required to make it 
work.

That being said, the below responses use ":>" as the separator.

On Fri, Mar 21, 2025, at 12:15, Rokas Šleinius wrote:
> 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!

Hey Rokus,

Generally, nested classes do not get inherited in languages that support them 
for several reasons: they are mostly scoped types, and they help organize the 
code and reduce namespace pollution but aren't considered part of the class 
hierarchy. 

If we were to be an outlier and support inheritance, the options for 
inheritance basically boil down to either enforce inheritance in child classes 
and end up in some really weird situations; or deal with "LSP violations."

Let's take a look at an example where we want to add a nested child class that 
illustrates "weird" if we turn on forced inheritance:

abstract class Shipment {
  protected final class Items {}
}

Then when we want to add our own Items in our subclass:

class InternationalShipment extends Shipment {
  protected class Items extends Shipment\items {} // ummmm
}

So, now we have an abstract class that cannot be implemented with any nested 
class called "Items" even if they are unrelated, they are forcibly related.

If we don't enforce inheritance but simply allow inherited classes to "just 
work" you can end up with this code:

abstract class Shipment {
  protected class Items {}
}

class LocalShipment extends Shipment {
  protected function deliver(self:>Items $items);
}

and then you later come along and add it as a class:

class LocalShipment extends Shipment {
  protected class Items {}
  protected function deliver(self:>Items $items) {} // now it refers to a 
totally different type!
}

This can make refactoring a total nightmare, whereas the current solution would 
have to look like this:

class LocalShipment extends Shipment {
  protected function deliver(Shipment:>Items $items) {}
}

You can add a class with the name `Items` if you want, and there is no problem 
at all; everyone reading it knows you mean "*this* particular class's Items" 
and they don't have to search the class to see if it is inherited or not.

On Sun, Mar 23, 2025, at 16:17, Larry Garfield wrote:
> On Wed, Mar 12, 2025, at 5:10 AM, Rob Landers wrote:
> 
> > Hello internals,
> >
> > I've made some major updates to the text of the RFC to clarify 
> > behaviors and revisited the implementation (which is still under 
> > development, though I hope to have a draft by the end of this weekend). 
> > Here's a broad overview of what has changed in inner classes:
> >
> > - Accessing inner classes is done via a new token: ":>" instead of "::".
> > - Inner classes may now be infinitely nested.
> > - Inner classes may be declared `abstract`.
> > - Documented changes to ReflectionClass.
> > - Usage of `static` to refer to inner classes is restricted to prevent 
> > accidental violations of LSP.
> >
> > Otherwise, there are not any big changes, but a lot of time was spent 
> > clarifying behavior and expanding on the reasoning for those decisions 
> > in the RFC itself.
> >
> > — Rob
> 
> I've been following this thread with interest, and at the moment I'm honestly 
> undecided.  I certainly see the use cases for this functionality (whatever it 
> gets named), but as a practical matter it sounds like it introduces a lot of 
> extra clunk and complexity.  And it seems like the use cases could be 
> addressed as well with either fileprivate or module-private.  (The former 
> being considerably less work.)
> 
> So, how would nested classes compare to fileprivate, in terms of ability to 
> solve the problem space?  As I understand it, the goal is:
> 
> 1. Classes that can be instantiated only by the class that uses them.
> 2. But can be returned from that class to a caller and reused as appropriate.
> 
> The autoloading question (loading a whole file for just an implementation 
> detail value object) is not one that carries much weight for me, as that's a 
> user-space question, not an engine question.  (Nothing in PHP itself says you 
> cannot put 20 3 line classes or enums together in one file.  It's just PSR-4 
> that says not go. Even composer would allow it if configured properly)  So 
> how would the less-complicated alternative compare?
> 
> --Larry Garfield

Hey Larry,

I think file-private would/could be useful, but that only limits you to a 
"private" scope, which severely hampers what you can do with it. If we went 
with "module-private" (rhetorical question: what is a module?), but then you 
wouldn't be able to have "private" scope.

With nested/inner classes, for example, you can put a protected class on a 
class or interface and access it only from those that use it, regardless of 
what file or "module" (namespace?) they are in; the logic can be encapsulated 
to where it is used, not where it is defined.

interface Shipment {
  protected class Items {}
  public readonly class Destination {}
  function deliver(self:>Destination $destination);
}

class InternationalShipment implements Shipment {
  private function handle(Shipment:>Items $items) {}

  public function deliver(Shipment:>Destination $destination) {}
}

However, you can use private nested/inner classes to encapsulate the logic to 
where it is defined instead, like file-private would give you.

The goal here isn't to only to reduce "namespace pollution" but also to 
encapsulate related logic integrally connected to the outer class's behavior. 
Since you've referenced Kotlin a number of times, I assume you are familiar 
with the concept of nested classes there? This is extremely similar.

It's also worth pointing out that the nested/inner classes are more like 
"friend classes", just like in other languages, which is something you don't 
get from file-private or module-private. Oh, and I don't think file-private or 
module-private is less complicated :) just a different kind of complicated... 
For the latter, I'm guessing we'd bikeshed for months just deciding what a 
"module" even is. In any case, I'm going to be tackling that (module-private) 
next-ish; regardless of whether this RFC passes. The plumbing and 
implementation details can be modified for that purpose, and the changes are 
fairly trivial. Both of these, together, would be immensely powerful tools. 
Though, even just one of them would still be awesome.

— Rob

Reply via email to