On Tue, Jul 22, 2025, at 02:10, Eric Norris wrote: > On Sun, Jul 20, 2025 at 4:19 PM Rob Landers <rob@bottled.codes> wrote: > > > > > > > > On Sun, Jul 20, 2025, at 19:18, Eric Norris wrote: > > > > Hi Rob, > > > > I'm going to respond to a few points from earlier emails here instead > > of each one. > > > > On Sat, Jul 19, 2025 at 6:13 AM Rob Landers <rob@bottled.codes> wrote: > > > > > > > > > > > > On Sat, Jul 19, 2025, at 12:09, Claude Pache wrote: > > > > > > > > > > > > Le 19 juil. 2025 à 09:46, Rob Landers <rob@bottled.codes> a écrit : > > > > > > > > > > > > On Sat, Jul 19, 2025, at 03:04, Claude Pache wrote: > > > > > > > > > > > > > > > Le 19 juil. 2025 à 00:41, Rob Landers <rob@bottled.codes> a écrit : > > > > > > > > > The original author (Nikita) suggested that there's nothing in the > > > original design that precludes accessors -- and highlights languages > > > where there are both and they are doing just fine more than 5 years later. > > > > > > > > > Hi Rob, > > > > > > It is indeed entirely reasonable to have both readonly properties and > > > hooked properties (aka accessors), and today PHP has indeed both of them > > > (and even asymmetric visibility on top of that, as a separate feature > > > contrarily to C#). But it doesn’t mean that it is reasonable for the > > > *same* property to be *both* readonly and hooked, which is the point that > > > is currently disputed. — What do the other languages allow? Is it > > > possible to define a readonly property with a user-defined getter? > > > (Disclaimer: Even if one of them allows such a thing, I’ll still think > > > that it is a bad idea.) > > > > I do not believe I was cherry picking; I share Claude's interpretation > > here that the RFC says that readonly doesn't prevent the language from > > adopting accessors (hooks) later, which is what the language did, and > > notably it did without applying them to readonly properties. > > > > Could you perhaps walk me through your thinking when the RFC claims > > that `assert($prop === $prop2)` "always holds"? > > > > > > Hi Eric, > > > > I think that is explaining how Nikita arrived at some of the conclusions > > (it is in the rationale section, after all) and shouldn't be taken as > > literal. Here are some snippets that I think should be looked at a little > > more closely, that seem to align with the vision of the feature, in the > > context of getters/setters: > > > > "The closest alternative is to declare the property private, and only > > expose a public getter" -- indicates to me that a single public getter > > should, in fact, be considered readonly. > > The very next line says, "This doesn't actually make the property > readonly, but it does tighten the scope where modification could occur > to a single class declaration." I interpret this as "the property can > still be written to *internally* many times, but at least you don't > have to look very far to confirm that this doesn't happen".
I think the intent was to show how public readonly could be implemented at that time. However, if you want private readonly, you cannot have that in the version of PHP at that time. > Or to put it another way, the RFC is claiming that this makes it so > that a *consumer* cannot mutate the value freely, only the class can, > but *this is not in fact readonly*. It takes the position that the class itself can be the consumer and provides a way to express it through write-once properties. > > "Support for first-class readonly properties allows you to directly expose > > public readonly properties, without fear that class invariants could be > > broken through external modification" -- indicates to me that interior > > mutability (and maybe nondeterminism) should be at the discretion of the > > interior, not the exterior contract. > > I think this is building on top of the above, indicating that readonly > allows it to be clear that no one can write to this property multiple > times, even though it is publicly exposed. It doesn't say "no one" it only says "class invariants" which may mean different things in different designs and systems. If your design specifies that a value is invariant, that could mean it is "always constant", or on the other extreme, could mean it is "always random". When the design aligns with readonly, then you can have the engine enforce it. > > "...readonly properties do not preclude interior mutability. Objects (or > > resources) stored in readonly properties may still be modified internally" > > -- further specifies that interior mutability is allowed; only exterior > > mutability isn't. > > > > It doesn't define "interior mutability" stringently, so we can have > > differing opinions on that; but it seems to be "the value inside the > > property" which may or may not be an object, resource, or a hook's result. > > I do have a differing opinion, and will quote the first line of the > RFC - "This RFC introduces a readonly property modifier, which > prevents modification of the property after initialization." It seems > pretty clear to me that this is talking about ensuring that a property > is initialized once and the property *itself* will not change, but if > the property is an object, the object's properties are free to change. > > I agree that the readonly keyword itself leaves room for > interpretation as I mentioned in my earlier email, but I just can't > seem to read this RFC as anything other than promising immutability, > especially considering this first line. You can also implement this today, without using readonly: class Foo { public int $bar { get => $this->bar; set => empty($this->bar) ? $this->bar = $value : throw new LogicException("nope"); } } https://3v4l.org/2JagR#v8.4.10 Should I be able to mark this class as readonly? I would think so. That's the question this new RFC is proposing, in my mind. It is saying that hooks are allowed to be readonly, allowing me to specify my own invariants, to a degree, and having the engine enforce them where it can while also signifying consumers there are some obvious invariants. Sure, you could do other shenanigans in the get-hooks, but it is a programming language -- there will always be shenanigans. > > > > > > —Claude > > > > > > > > > Hey Claude, > > > > > > From what I've seen in other languages, this combination is fairly common > > > and not inherently poblematic. > > > > > > - C# allows get hooks with user-defined logic, even in readonly structs. > > > - Kotlin uses val with a get() body, which is readonly from the > > > consumer's perspective, even though the value is computed. > > > - TypeScript allows a get-only accessor which acts readonly. > > > - Swift also allows get-only computed accessors/hooks. > > > > (disclaimer, I am not a C# expert) > > > > It seems that C# has both fields and properties, and a readonly field > > seems to align with what a few of us are claiming is how PHP readonly > > properties should work. C# properties are more open-ended, and don't > > actually support the readonly keyword - you can make them "read only" > > in the sense of only having a get accessor (and you can mark that > > accessor itself as readonly), but this is different from readonly > > fields, which enforce a contract about the mutability of the field. > > > > I think that C# fields, both readonly and not, match PHP's properties > > without hooks. C# properties - which I believe cannot be *marked* > > readonly, but they can be *made* read only, i.e. only exposing a get > > accessor - match PHP's properties with hooks. > > > > > > I would prefer to simply allow specifying a class as readonly so long as > > only get hooks are present. However, I'm ok with saying that get hooks may > > themselves be readonly which accomplishes the same thing and is easier to > > reason about. > > I'm sorry, I don't follow the second sentence. My main point was that > C#'s readonly *fields* cannot have accessors, which I believe are 1:1, > invariant-wise, with PHP's readonly properties. C#'s properties are > 1:1 with PHP's properties with hooks. > > C# does have readonly accessors for properties, but this is a separate > invariant that makes the compiler ensure that the get acessor itself > cannot modify the state of the object, and is separate from the notion > of a readonly field. Correct! Yes, sorry. I didn't explain well. A readonly class in PHP is effectively copy-pasting readonly before every property. This means we either need to redefine what that means, or allow readonly to be defined on hooks so that a class using hooks may be defined readonly in special circumstances. > > > > > > > > > Hi Rob, > > > > > > The main problem is that we don’t agree on the meaning of “readonly > > > property”. > > > > I agree with Claude here. Rob, I'm curious how you interpret the > > contract of readonly. Do you think that it is a contract about > > writability to consumers of the class? In an earlier email, you said: > > > > > In the example above, there is nothing mutable about it. It is "read > > > only" from a public contract point-of-view, we just can't mark it as a > > > readonly class. > > > > In this most recent email, you said: > > > > > The error you get when trying to modify the "get-only accessor" is > > > `Error: Cannot assign to 'name' because it is a read-only property` > > > > > > Thus, readonly is implied by the get-only accessor, there's no need to > > > specify it directly. > > > > Would you say this is how you are interpreting readonly? That it is a > > contract to consumers of the class that they can read but cannot write > > to it? > > > > > > That's the definition in the RFC, from my reading of it: the ability to > > expose bare properties without worrying that someone else will modify it. > > One might argue that there really isn't a reason for readonly anymore, > > since we have aviz + hooks. Or rather, that readonly could be just a > > shorthand for that -- plus the ability to write to those properties > > exactly-once. I suspect this is where "write-once" is coming from elsewhere > > in the thread, since that is the only difference between "manual readonly" > > and "engine-powered readonly". > > I would not argue that there isn't a reason for readonly anymore due > to aviz, I would argue that readonly means something different from > aviz, which is exactly how I read the RFC. > > Can I ask what you would like to use the readonly keyword for? I would > like to use it to, as the RFC states, "[prevent] modification of the > property after initialization". Would you also like to use it to > prevent modification of the property after initialization, or would > you like to use it to signify that it is `public protected(set)`? The > latter, to me, is an implementation detail of readonly and not the > main point; if you want to declare asymmetric visibility, use > asymmetric visibility. The problem I've run into, especially in 8.3+ is that I like value objects. Ever since reading Domain Driven Design by Eric Evans in 2006-07, I love how simply using them can remove a ton of procedural code, enforce invariants, and make reasoning through code so much simpler (see Chapter 10). The readonly keyword simplified that greatly, however, readonly has been neutered compared to regular classes in the last couple of versions. There are so many edge cases and non-implemented features with them -- mostly due to this exact argument you are making -- that they're nearly worthless to actually define immutable value objects in today's PHP. This is why I ever even began with the Records RFC ... between all the edge cases and weird behaviors, I want to create "actually immutable value objects" that are just as powerful as regular classes and leave readonly for when you need immutable parts of classes. > > (including a snippet from an earlier email) > > > > > I think an init hook is out of the question for 8.5, so I'm not even sure > > > it's worth discussing. > > > > I don't agree that it's not worth discussing alternative solutions to > > the problems the RFC authors intend to solve. I think it has been > > expressed elsewhere, but I believe we should take the time to make the > > best decisions for the language and not rush to add a controversial > > feature. > > > > > > Would an init hook actually solve it though? An init hook would basically > > just be a constructor / default value outside of the constructor that > > specifies an instance-level constant. The readonly property RFC goes into > > some details here: > > > > "As the default value counts as an initializing assignment, a readonly > > property with a default value is essentially the same as a constant, and > > thus not particularly useful. The notion could become more useful in the > > future, if new expressions are allowed as property default values. At the > > same time, depending on how exactly property initialization would work in > > that case, having a default value on a readonly property could preclude > > userland serialization libraries from working, as they would not be able to > > replace the default-constructed object. Whether or not this is a concern > > depends on whether the property is initialized at time of object creation, > > or as an implicit part of the constructor (or similar). As these are open > > questions, the conservative choice is to forbid default values until these > > questions are resolved." > > An init hook would not be a default value outside of the constructor > that specifies an instance level constant, what gives you that > impression? The problem that people have pointed to is lazy > initialization, e.g. fetching something from the database when > accessed. This very RFC mentions lazy initialization as the use-case > for get hooks here: > https://wiki.php.net/rfc/readonly_hooks#orms_and_proxies. > > I believe that an init hook would solve this problem instead, and > maintain the invariant of immutability for readonly properties. > > > When would the init hook get called? And in what order? This brings back > > some memories of solving some Java language bugs where static variables > > wouldn't be initialized in time (they're non-deterministic) causing strange > > crashes, or the potential to "deadlock" yourself and you need to read > > properties in a specific order in order to ensure the object gets > > initialized correctly. > > Tim and I separately offered answers to these questions earlier; in > any case I believe it's a tractable problem if our initial suggestions > aren't enough. > > > This isn't something that can be solved in a few weeks. Someone(s) needs to > > sit down and think through all the possibilities and then create a > > definition of an init hook that is better than a constructor. > > I agree this might not be solved in a few weeks, but since I don't > think we need to rush this before 8.5, I don't see this as a problem. I honestly haven't read the arguments for init-hooks. Mostly because I saw that part of the conversation and it reminds me of when other people start proposing their ideas in other people's RFCs. Sometimes they're good ideas, sometimes their not, but I leave that to the author to digest and make changes to the RFC. I don't agree with init-hooks for the reasons I gave, but until the RFC author incorporates it into the RFC, I tend to ignore those types of things. The author is the subject-matter expert and they're the ones that know if something is a good idea or not. Engaging in those parts of the discussions just leads to off-topic discussions. — Rob