On Fri, Jul 11, 2025, at 02:40, Nick wrote:
> Hey Rob,
> 
>> On 11. Jul 2025, at 01:43, Rob Landers <rob@bottled.codes> wrote:
>>> 
>>> Nick previously suggested having the get-hook's first return value cached; 
>>> it would still be subsequently called, so any side effects would still 
>>> happen (though I don't know why you'd want side effects), but only the 
>>> first returned value would ever get returned.  Would anyone find that 
>>> acceptable?  (In the typical case, it would be the same as the current 
>>> $this->foo ??= compute() pattern, just with an extra cache entry.)
>>> 
>>> --Larry Garfield
>>> 
>> 
>> I think that only covers one use-case for getters on readonly classes. Take 
>> this example for discussion:
>> 
>> readonly class User {
>>     public int $elapsedTimeSinceCreation { get => time() - $this->createdAt; 
>> }
>>     private int $cachedResult;
>>     public int $totalBalance { get => $this->cachedResult ??= 5+10; }
>>     public int $accessLevel { get => getCurrentAccessLevel(); }
>>     public function __construct(public int $createdAt) {}
>> }
>> 
>> $user = new User(time() - 5);
>> var_dump($user->elapsedTimeSinceCreation); // 5
>> var_dump($user->totalBalance); // 15
>> var_dump($user->accessLevel); // 42
>> 
>> In this example, we have three of the most common ones:
>>  1. Computed Properties (elapsedTimeSinceCreation): these are properties of 
>> the object that are relevant to the object in question, but are not static. 
>> In this case, you are not writing to the object. It is still "readonly".
>>  2. Memoization (expensiveCalculation): only calculate the property once and 
>> only once. This is a performance optimization. It is still "readonly".
>>  3. External State (accessLevel): properties of the object that rely on some 
>> external state, which due to architecture or other convienence may not make 
>> sense as part of object construction. It is still "readonly".
>> You can mix-and-match these to provide your own level of immutability, but 
>> memoization is certainly not the only one. 
>> 
>> You could make the argument that these should be functions, but I'd posit 
>> that these are properties of the user object. In other words, a function to 
>> get these values would probably be named `getElapsedTimeSinceCreation()`, 
>> `getTotalBalance`, or `getAccessLevel` -- we'd be writing getters anyway.
>> 
>> — Rob
> 
> Please remember that the RFC will allow `readonly` on backed properties only, 
> not on virtual hooked properties. 
> Nothing from your example would work, and it would result in:
> Fatal error: Hooked virtual properties cannot be declared readonly
> My proposed alternative implementation with caching addresses the concern 
> Claude and Tim had and will make this hold:
> 
> ```php
> class Unusual
> {
>     public function __construct(
>         public readonly int $value {
>             get => $this->value * random_int(1, 100);
>         }
>     ) {}
> }
> 
> $unusual = new Unusual(1);
> var_dump($unusual->value === $unusual->value); // true ```
> 
> – Nick

Hey Nick,

I was merely illustrating the different types, you can simply replace them with 
non-virtual examples. To your idea of forced memoization, I'm not sure that's a 
good idea. In cases where its a single execution context per request, it may be 
fine, but in cases of worker mode on frankenphp, where readonly objects may 
live for quite awhile, it may not be. Generally, memoization is an 
optimization, not a property of a value.

In your example above, this breaks the principle of least astonishment, and 
potentially violates referential transparency due to the calculation being 
non-pure. Referential transparency basically says we can can replace the use of 
the variable with it's value (in this case, $ususual->value with 
$unusual->backing_value * random_int(1, 100)). If it is memoized, we cannot. 
There isn't a way to express that it is one value the first time (random) and a 
different value the next time (the previous computation). This is a bit 
dangerous because the programmer has no way to reliably reason about the code 
as if the expression can be substituted freely.

— Rob

Reply via email to