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".

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*.

> "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.

> "...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.

> >
> > —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.

> >
> >
> > 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.

> (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.

Reply via email to