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