On Fri, Aug 5, 2022, at 12:08 PM, Larry Garfield wrote:
> Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3:
> Asymmetric Visibility.
>
> https://wiki.php.net/rfc/asymmetric-visibility
>
> Details are in the RFC, but it's largely a copy of Swift's support for the
> same.
Hi folks. Ilija and I have been discussing and experimenting extensively based
on the feedback to the first draft. Here's our thoughts so far, some
additional data, and a request for feedback. (These changes haven't been made
to the RFC just yet, as there's outstanding questions for which we want
feedback below.)
1. Rather than `readonly` being forbidden on asymmetric properties, it's better
to redefine it as "write once, and if no `set` visibility is defined, make it
private." That would still allow for `public protected(set) readonly string
$blah` (to work around existing limitations in `readonly`), but also more or
less preserve the current syntax in a logical way.
2. Given that... we're not sure that it makes sense to have any visibility
scope other than `get`/`set`. `once` becomes unnecessary with that
understanding of `readonly`, and we cannot think of any other examples that are
not a stretch. So that axis of extensibility is of minimal importance.
3. Since there has been some confusion, our goal *is* to implement property
accessors in the future as a follow-on to this RFC. However, conceptually
property "hooks" and asymmetric visibility are separate features. They also
both have ample bikeshed potential. That's why we're proposing them as two
separate RFCs. (Technically property hooks could also be done first, but this
one is smaller.) That said, it's become obvious that we cannot avoid
discussing property hooks at the same time since the syntax does overlap. So
in the discussion below I will include property hooks, but deliberately gloss
over parts that are not relevant right now. Please humor me and do the same.
:-)
4. In concept, there's two main syntactic models for property hooks/accessors:
Javascript/Python style (annotated methods) and Swift/C# style (method-esque
code blocks on properties). We were trying to keep our options open and avoid
a syntax that locks us into one or the other. However, we've concluded that
the annotated methods style just wouldn't work at all in a language with
explicit types and visibility controls. We've therefore made the executive
decision that only the Swift/C# style is an option.
5. That leaves the basic question of "where to put the visibility," as Swift
and C# put them in different places. (There are other differences not relevant
for now.)
Swift:
```swift
class User {
public private(set) var first: String
public private(set) var last: String
public private(set) fullName: String {
get { ... }
set { ... }
}
}
```
C#:
```csharp
class User {
public string first { get; private set; }
public string last { get; private set; }
public string fullName {
get { ... }
private set { ... }
}
}
```
The relevant difference is that in Swift, all visibility is on the left of the
property while in C# it is split between the left and right.
The main argument for Swift-style: Keep everything in one place, and if there's
no property hooks we don't even have to think about it.
The main argument for C#-style: It avoids repeating a keyword (eg, `set`) if
using both features.
It *may* be possible to omit the `get` in the C# example above if it's just the
default. Ilija is still working to figure that out.
## References make everything worse
One other caveat to consider is (naturally) references. A `get` hook cannot
return by reference, as that would allow setting the value via its reference
and bypassing a `set` hook.
A side effect of that restriction is that arrays cannot be modified in place on
properties with a `get` hook. That is, assuming the `$bar` property is an
array that leverages the extra hooks above (by whatever syntax), the following
is not possible:
```php
$f = new Foo();
$f->bar[] = 'beep';
```
That would require returning `$bar` by reference so that the `[]` operation
could apply to it directly. However, that then bypasses any restrictions
imposed by a `set` hook (such as the values had to be integers). The
following, however, would be legal:
```php
$f = new Foo();
$b = $f->bar;
$b[] = 'beep';
$f->bar = $b;
```
That problem wouldn't happen if all we're changing is visibility, however.
That means in the C# style, we would need to differentiate whether the block on
the right is intended to disable references or not. Ilija has proposed `raw`
to indicate that. That would mean:
```php
class Test
{
public $a { private set; }
public $b { private raw; }
public function inScope()
{
echo $this->a; // echo $this->getA();
$this->a = 'a'; // $this->setA('a');
echo $this->b; // echo $this->b;
$this->b = 'b'; // $this->b = 'b';
}
}
$test = new Test();
echo $test->a; // echo $test->getA();
$test->a = 'a'; // Error, set operation not accessible
echo $test->b; // echo $test->getB();
$test->b = 'b'; // Error, set operation not accessible
```
The take-away for the asymmetric visibility only case is that we would need to
use `raw` instead of `set`, in order to avoid confusion later with accessor
hooks and whether or not to disable references.
The Swift-style syntax, as it does not require any hook-like syntax to just
control visibility, does not have this problem. There is no ambiguity to
resolve, so no need for a `raw` keyword.
## Common use cases
While there are a myriad of possible ways to use both asymmetric visibility and
property accessor hooks, we anticipate three usage patterns to predominate:
1. Public read, private/protected write, but that's it.
2. Public read, public write, but some extra validation on set.
3. Public lazily-derived value, no write.
In particular, it seems to us (though we have no precise data to back it up)
that it is unlikely that both asymmetric visibility and property hooks would be
used at the same time, in the typical cases. That should inform the discussion
about syntax.
## Comparison
For the moment I'm just going to use Swift-style as-is. There's a discussion
later to be had about minor variations on it, as suggested by a few people in
the previous thread. Stand by.
Length comparison:
```php
// Current code, for comparison
public readonly string $first;
// Swift-ish
public private(set) string $first;
// C#-ish, note "raw" because there's no hook
public string $first { get; private raw; }
```
Both options suggest possible shorthands:
```php
// Current code, for comparison
public readonly string $first;
// Swift-ish
private(set) string $first;
// C#-ish, assuming it can be done
public string $first { private raw; }
```
If combining with `readonly`, we get:
```php
// Current code, for comparison
public readonly string $first;
// Swift-ish
public protected(set) readonly string $first;
protected(set) readonly string $first;
// C#-ish
public readonly string $first { get; private set; }
public readonly string $first { private raw; }
```
If we mix in for-reals accessor hooks, we get:
```php
// Swift-ish
public private(set) string $first {
get { ... }
set { ... }
}
// C#-ish, note now we're using "set".
public string $first {
get { ... }
private set { ... }
}
```
If other hooks are added (Swift has `beforeSet` and `didSet`, and we intend to
include those as well for PHP), they don't really make sense to have visibility
modifiers on them. It's arguable if `isset` or `unset` (to mirror the existing
magic methods) would want those. Does it make sense to have "public read,
public set, but private unset"? Or is `unset` just controlled by `set`?
Unclear.
Another complication is that, for forward compatibility, we would need a way to
disable references on no-hook properties so that hooks could be added later
without a change in reference behavior. Given the postfix syntax for hooks, we
believe the most natural way to do that would be:
```php
// Swift-ish
public string $foo {}
// C#-ish
public string $foo {}
// which is shorthand for:
public string $foo { public raw; }
```
Again, the C# model implies a bit more complexity and nuance.
## Questions
Question 1: Which general model (Swift or C#) is preferable, and why?
(At the moment, Ilija and I disagree on which one is preferable. Hence, poll.)
## If Swift
If we go with Swift-style, several people suggested alternative syntax options.
This is *mostly* a bikeshed question, so I will just list them:
```php
public private(set) string $first;
public private:set string $first;
public:private string $first;
public private string $first;
public(set: private) string $first;
```
Some of which have natural shorthands:
```php
private(set) string $first;
private:set string $first;
:private string $first;
N/A
N/A
```
Question 2: IF we go with Swift-style, which of the above syntaxes would you
prefer, and why?
Please answer both questions only via this poll so can collect actual data.
https://docs.google.com/forms/d/e/1FAIpQLSefq15VvGNIXSnQaMTl3RW451w0E8oesny8c4PLqmKl8HhQ-Q/viewform
Thank you all for your time and input.
--Larry Garfield and Ilija Tovilo
--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php