Re: RFC: short and inner classes

From: Date: Tue, 25 Mar 2025 20:29:16 +0000
Subject: Re: RFC: short and inner classes
References: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  Groups: php.internals 
Request: Send a blank email to [email protected] to get a copy of this message
On Tue, Mar 25, 2025, at 19:51, Larry Garfield wrote:
> On Tue, Mar 25, 2025, at 6:59 AM, Rob Landers wrote:
> 
> >> When I say module scope, I'm referring to something along the lines that Arnaud
> >> and I were exploring a while back.  tldr, "cluster of files with a common namespace root, which
> >> can get loaded together."  It was mostly about performance, but did offer module-private as
> >> well, with some nice potential.  At the moment it's stalled out on "there's nasty
> >> hard edge cases and we're not sure if it's worth it" concerns.
> >> 
> >> Concept brain dump here: https://github.com/Crell/php-rfcs/blob/master/modules/spec-brainstorm.md
> >> Code exploration from Arnaud here: https://github.com/arnaud-lb/php-src/pull/10
> >> 
> >> Still well short of RFC state, of course, but provided for context.
> >
> > My email has been broken for a few days, so sorry for the late response...
> 
> No worries, I'm about to leave town myself. :-)
> 
> >> In this case, I am not seeing what the nesting gets you.  Making Destination a normal
> >> class doesn't hurt anything here, does it?
> >
> > I wasn't intending to write a definitive example, just to illustrate 
> > it. A better example might be a lazy cache, using hooks to notify the 
> > outer class it has possibly been updated: 
> >
> > (note this is using the new syntax, but this syntax is NOT currently 
> > reflected in the RFC text, yet)
> >
> > namespace Caching;
> >
> > class Cache {
> >     private array $items = [];
> >     private array $dirty = [];
> >
> >     public function getItem(string $key): CacheItem {
> >         return $this->items[$key] ?? ($this->items[$key] = new 
> > CacheItem($this, $key, null));
> >     }
> >
> >     public function saveChanges(): void {
> >         foreach ($this->dirty as $key => $value) {
> >             echo "Saving $key to persistent storage\n";
> >         }
> >         $this->dirty = [];
> >     }
> >
> >     private function markDirty(string $key): void {
> >         $this->dirty[$key] = $this->items[$key];
> >     }
> >
> >     public class CacheItem {
> >         public string|null $value {
> >             get {
> >                 return $this->_value;
> >             }
> >             set {
> >                $this->_value = $value;
> >                $this->cache->markDirty($this->key);
> >             }
> >         }
> >
> >         public function __construct(
> >             private readonly Cache $cache,
> >             public private(set) string $key,
> >             private string|null $_value,
> >         ) {}
> >     }
> > }
> >
> > $cache = new Cache();
> > $item1 = $cache->getItem('foo');
> > $item1->value = 'bar';
> >
> > $cache->saveChanges();
> >
> > This outputs:
> >
> > Saving foo to persistent storage
> >
> > This provides for a simple API for outside the Cache class: getItem() 
> > and saveChanges(); that's it. For CacheItem's, you only have the props 
> > for the key or value, and updating the value marks the item as "dirty".
> >
> > Currently, if you want this same API, the only way to do this is by:
> >
> > 1. using reflection,
> > 2. using a closure and binding it to the other class's scope,
> > 3. providing a listener + observer (more formal version of [2]), or
> > 3. iterating over items while saving to find dirty items.
> >
> > Those choices are not ideal (IMHO).
> >
> > Reflection feels "hacky" as does using a bound closure. The 
> > listener/observer pattern is probably overkill here, unless you already 
> > have one lying around (i.e., using symfony/laravel or a related 
> > framework), but still might require making some of the "internal" api 
> > public. Finally, iterating over all the items to find dirty ones is 
> > naive and inefficient.
> >
> > Hopefully this is a better illustration of what nesting provides?
> >
> > -- Rob
>  
> I have a similar if less involved use case in my ordering library, though it doesn't need
> the look-back functionality.
> 
> So the use case that nested classes would enable that isn't currently covered by
> "just use separate files and @internal, deal" is around the lesser class having private
> access to the greater class.  Which... I believe fileprivate and modules would also address, albeit
> in a different way, yes?  (Presuming fileprivate is available on properties, not just the class
> itself.)
> 
> --Larry Garfield

Well ... to use the best engineering response: "it depends..."

File-private, in my mind, is different; such as when the classes are distinctly unrelated with
respect to each other, but nobody else should be able to instantiate or use the private class. It is
like it doesn't exist outside that file. For example, something like a log formatter or a
default strategy implementation. Personally, I'd feel that file-private should be kept as
simple as possible and limit it to "top-level" things, but that doesn't necessarily
have to be the case. If we did allow it on methods/properties, when mixing it with regular
visibility, what happens? fileprivate public private(set) ... means what exactly? I
assume we probably wouldn't allow that particular scenario, and maybe fileprivate
on a property means public in the file, but private outside the file. But
then how would that intersect with inheritance? My point is that I don't think there is an
intuitive answer to these behaviors, but at least nested/inner classes, while not 100% intuitive
either, at least are available in other languages, so its behavior will be familiar.

If we were to target php 9, then we could simply redefine what private even means, similar to
Rowan's findings up thread (emphasis mine), and not even have a special keyword:

On Mon, Mar 24, 2025, at 20:22, Rowan Tommins [IMSoP] wrote:
> If you'll excuse a brief philosophical detour, I noticed something interesting in the
> Swift documentation: the description of nested classes doesn't describe any special scope
> access. Instead, the description of "access levels" defines "private" in a way
> that naturally includes them:
> 
>> Private access restricts the use of an entity to the enclosing declaration, and to
>> extensions of that declaration that *are in the same file*.
> 
> It's a subtly different framing - nested types aren't breaking into the private
> entity, the "private" keyword is explicitly letting nested types in.

Module-private, on the other hand, would be a huge boon to php; for a number of reasons. After
reading your brain-dump, it would be really interesting if we could package a binary representation
of the module alongside the module itself -- maybe not in an initial version, but later -- so that
it could basically be "pre-loaded" right into OPcache... But I digress. Your module idea
looks far simpler to implement than this one :D 

I'd be happy to give it a go, if you're up for it and no one else is working on it. It may
be a few months, though; if this one passes, I want to go for short-classes next, plus I assume
there is an expectation that I would fix bugs.

— Rob


Thread (102 messages)

« previous php.internals (#126939) next »