Hi Rowan

On Sat, Mar 16, 2024 at 8:23 PM Rowan Tommins [IMSoP]
<imsop....@rwec.co.uk> wrote:
>
> I still think there will be a lot of users coming from other languages, or 
> from using __get and __set, who will look at virtual properties first. Making 
> things less surprising for those people seems worth some effort, but I'm not 
> asking for a complete redesign.

For clarity, you are asking for a way to make the "virtualness" of
properties more explicit, correct? We touch on a keyword and why we
think it's suboptimal in the FAQ section. Unfortunately, I cannot
think of many alternatives. The `$field` variable made it a bit more
obvious, but only marginally.

I do believe that, for the most part, the user should not have to
think much about whether the property is backed or virtual. The
behavioral differences are mostly intuitive. For example:

```php
class Test {
    // This property has a set hook that writes to the backing value. Since
    // we're using the backing value, it makes sense for there to be a way to
    // retrieve it. Without that, it wouldn't be useful.
    public $prop {
        set {
            $this->prop = strtoupper($value);
        }
    }

    // Similarly, a property with only a get hook that accesses the backing
    // value would need a way to write to the property for the get to be useful.
    public $prop {
        get => strtoupper($this->prop);
    }

    // A property with a get hook that does not use the backing value does not
    // need an implicit set operation, as writing to the backing value would be
    // useless, given that nobody will read it.
    public $prop {
        get => 42;
    }

    // Similarly, in the esoteric write-only case that does not use the backing
    // value, having an implicit get operation would always lead to a
    // "uninitialized property" error, and is not useful as such.
    public $prop {
        set {
            echo "Prop set\n";
        }
    }
}
```

Furthermore, `serialize`, `var_dump` and the other functions operating
on raw property values will include the property only if it is backed.
This also seems intuitive to me: If you never use the backing value,
the backing value would always be uninitialized, so there's no reason
to include it.

One case that is not completely obvious is lazy-initialized properties.

```php
class Test {
    public $prop {
        get => $this->prop ??= expensiveOperation();
    }
}
```

It's not immediately obvious that there is a public set operation
here. The correct way to fix this would be with asymmetric visibility,
which was previously declined. Either way, I don't consider this case
alone enough to completely switch our approach. Please let me know if
you are aware of any other potentially non-intuitive cases.

I will admit that it is unfortunate that a user of the property has to
look through the hook implementation to understand whether a property
is writable. As you have previously suggested, one option might be to
add an explicit `set;` declaration. Maybe it's a bit more obvious now,
after my previous e-mail, why we are trying to avoid this.

Apart from the things already mentioned, it's unclear to me whether,
with such `set;` declarations, a `get`-only backed property should
even be legal. With the complete absence of a write operation, the
assignment within the `set` itself would fail. To make this work, the
absence of `set;` would need to mean something like "writable, but
only within another hook", which introduces yet another form of
asymmetric visibility.

> > Dynamic properties are not particularly relevant today. The point was
> > not to show how similar these two cases are, but to explain that
> > there's an existing mechanism in place that works very well for hooks.
> > We may invent some new mechanism to access the backing value, like
> > `field = 'value'`, but for what reason? This would only make sense if
> > the syntax we use is useful for something else. However, given that
> > without guards it just leads to recursion, which I really can't see
> > any use for, I don't see the point.
>
> I can think of several reasons we *could* explore other syntax:
>
> 1) To make it clearer in code whether a particular line is accessing via the 
> hooks, or by-passing them 2) To make the code in the hooks shorter (e.g. 
> `$field` is significantly shorter than `$this->someDescriptiveName`) 3) To 
> allow code to by-pass the hooks at will, rather than only when called from 
> the hooks (e.g. having a single method that resets the state of several 
> lazy-loaded properties)
>
> Those reasons are probably not enough to rule out the current syntax; but 
> they show there are trade-offs being made.

Fair enough. 1 and 2 are reasons why we added the `$field` macro as an
alternative syntax in the original draft. I don't quite understand
point 3. In Kotlin, `field` is only usable within its associated hook.
Other languages I'm aware of do not provide a way to access the
backing value directly, neither inside nor outside the accessor.

> To be honest, my biggest hesitation with the RFC remains asymmetric types 
> (the ability to specify types in the set hook). It's quite a significant 
> feature, with no precedent I know of, and I'm worried we'll overlook 
> something by including it immediately. For instance, what will be the impact 
> on people using reflection or static analysis to reason about types? I would 
> personally be more comfortable leaving that to a follow-up RFC to consider 
> the details more carefully.

I personally do not feel strongly about whether asymmetric types make
it into the initial implementation. Larry does, however, and I think
it is not fair to exclude them without providing any concrete reasons
not to. I will spend time in the following days cleaning up tests, and
I will try my best to try to break asymmetric types. If I (or anybody
else) can't find a way to do so, I don't see a reason to remove them.

> Nobody else has raised that, beyond the syntax; I'm not sure if that's 
> because everyone is happy with it, or because the significance has been 
> overlooked.

Yes, unfortunately that's a classic problem in RFC discussions: Syntax
gets a disproportionate amount of attention.

Ilija

Reply via email to