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