On Sat, Jul 24, 2021, at 12:33 AM, Tobias Nyholm wrote:
> >> @Larry makes an argument to keep them: 
> >> 
> >>> Requiring parenthesis now leaves the option open in the future to make 
> >>> them optional when doing full mixed types.
> >> 
> >> 
> >> I don’t understand why we should require something that is not needed 
> >> simply because it would give us an option to remove it later… Could you 
> >> elaborate why this is important? (Im probably missing something)
> > 
> > The difference is if we make the decision to use the `?X&Y` syntax and we 
> > later realize it was a mistake then we are stuck with it. 
> > 
> > OTOH if we use the (X&Y)|null syntax and later realize it is okay to also 
> > allow `?X&Y` PHP could later be changed to allow it.
> > 
> > The later is the choice that manages future risk better.
> 
> I thought Larry was discussing `X & Y | null` vs `(X & Y) | null`. 
> I’ve dropped `?X&Y` because Derick had technical arguments against it. 
> 
> The way I see it, there is no benefit in requiring the parentheses in 
> `(X & Y) | null`. I suggest we use  `X & Y | null`.

Let me expand on my previous point (though those who have restated it are 
essentially correct).

1) Full mixed intersection and union types is something that we want to do in 
the future; it may or may not happen in 8.2, but it's something that should be 
considered on the informal roadmap.

2) Therefore, IF nullable intersection types are added now, they MUST be added 
in a way that is future-compatible (syntactically) with full mixed types in the 
future, as nullable intersection types are by definition (in PHP) a 
reduced-scope form of mixed types.  To do otherwise would create a needless 
inconsistency in the language.

3) The parsing logic for intersection types is already bonkers, and has to do 
some wonky shenanigans in order to work.

4) At this time, it is unclear if it will be *technically* possible to support 
`Foo&Bar|Baz` as a syntax.  Whether it is desireable or not is subjective, but 
whether it is possible at all is an unknown at this point.  Given the 
complexity already in place, it may not be possible, no matter how much anyone 
argues that it's "obvious" based on math.  If the technical answer is no, then 
the desireability question is entirely irrelevant.

5) Point 4 means that we simply don't know if `X&Y|null` will be extensible to 
X&Y|Z.  It may be; it may also be impossible or infeasible.  Point 3 means I 
would be highly skeptical of that being the case, and thus we should assume it 
is not.

6) That means there are a couple of possible end-states, assuming X&Y|null is 
implemented now:

A) X&Y|Z and (X&Y)|Z both work fine, whether Z is null or not.

B) X&Y|Z works iff Z is null; if it's not, then you must do (X&Y)|Z.

C) X&Y|null cannot remain supported once full mixed types are supported, at 
least not without still-more special casing and shenanigans.  That means either 
breaking BC, never implementing full mixed types, or having a wonky one-off in 
the engine forever.

D) Some other mechanism such as the sometimes discussed type aliases provides 
an alternate way to solve this problem.  (Eg, you have to pre-define a complex 
type Foo = X&Y|Z, and then in a function signature you can type either Foo or 
?Foo.)

As of right now, *we do not know which of those will happen*.  I think most 
would be fine with A as an outcome, but there is very significant risk of C 
happening, and C is a bad outcome.  Even if B could be done with a minimum of 
engine wonkiness, it would still be a sub-ideal state from a developer point of 
view.  And D is still a very open question that may complicate the whole 
situation further.

Thus, IF nullable intersection types are supported now, we're basically 
guessing on whether it will be possible to support mixed types in the future 
without parentheses.

If we guess that it will be possible, and we're right, spiffy!

If we guess that it will be possible, and we're wrong, we're in case C above. 
This is the worst outcome.

If we guess that it will not be possible, and we're right, spiffy!

If we guess that it will not be possible, and we're wrong, then people using 
nullable intersection types get a bonus new feature along the way.

Of those, the only not-happily-ever-after alternative is guessing the parens 
will be optional and being wrong.  Everything else works out OK.  Thus, we 
should guess that it will not be possible, as that can only lead to a positive 
outcome whether we're right or wrong.

Based on the above logic, I will not be voting for this RFC if it does not 
include parens, as it is too risky given the open unknowns around what will be 
possible in the future.

------

Separate from the technical argument, the question of how necessary it is:

The assertion that intersection types are useless without nullability is 
facetious and wrong.  Are they more useful with nullability?  Yes, I don't 
think that's really debatable.  But not-useful is incorrect.

PHP 7 scalar types *were* useful.  They *did* get used.  Just not in as many 
cases as they could have been.  However, I still die a little inside every time 
I have a nullable argument/return.  To me, nullable always means a design flaw, 
even if a small one.  (I still use it, but I hate that I have to.)  The 
overwhelming majority of my type declarations are not nullable, and so would 
work perfectly fine with intersection types.

I have no data on just how many libraries skipped 7.0 as a target version, but 
to assert that "everyone" did so because of the lack of nullability is simply 
absurd.  Here's a bunch of other perfectly good reasons why a library would 
have skipped 7..0 as a target, many of which I know projects did do:

1) PHP 7's unified variable syntax was a PITA to update to in some cases, as 
it's hard to test for.  By the time projects really worked all that out, 7.1 
was out anyway.

2) PHP 5.6 had an extra-long support lifespan, so there was less incentive to 
do so than usual.

3) Even today aggressively tracking new versions is unusual, based on the 
Composer stats.  6 years ago it was even less common.  What is super common is 
for projects to skip several required versions all at once when doing larger 
overhauls.  (That includes Symfony.)

4) Linux distributions come out less frequently than PHP versions, so they 
often skip over "officially supported" PHP versions.

So yeah, to pin the entirely of PHP 7.1 seeing a larger uptick than 7.0 on 
nullability is an entirely nonsensical and unsupported claim.  That may well 
have been true for some projects, but there is zero evidence that was the 
primary reason for most projects, or even a major reason at all.

I'm not against adding nullable intersection types now, but the "8.1 will be 
useless otherwise" rhetoric being used to argue for it is inappropriate and 
unjustified.

--Larry Garfield

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php

Reply via email to