On 21/03/2024 19:03, Robert Landers wrote:
I suppose we are taking this from different viewpoints, yours appears to be more of a philosophical one, whereas mine is more of a practical one.
My main concern is consistency; which is partly philosophical, but does have practical impact - the same syntax meaning the same thing in different contexts leads to less user confusion and fewer bugs.
But I also think there are real use cases for "error on anything other than either Foo or null" separate from "give me a null for anything other than Foo".
$x = $a as null; (or any other value, such as true|false) appears to have no practical purpose in this particular case.
There's plenty of possible pieces of code that have no practical purpose, but that on its own isn't a good reason to make them do something different.
"null" as a standalone type (rather than part of a union) is pretty much always pointless, and was forbidden until PHP 8.2. It's now allowed, partly because there are scenarios involving inheritance where it does actually make sense (e.g. narrowing a return type from Foo|null to null); and probably also because it's easier to allow it than forbid it.
That's not really what we're talking about anyway, though; we're talking about nullable types, or null in a union type, which are much more frequently used.
Further, reading "$x = $a as null", as a native English speaker, appears to be the same as "$x = null".
Well, that's a potential problem with the choice of syntax: "$x = $a as int" could easily be mistaken for "cast $a as int", rather than "assert that $a is int".
If you spell out "assert that $a is null", or "assert that $a is int|null", it becomes very surprising for 'hello' to do anything other than fail the assertion.
As I mentioned in the beginning, I see this mostly being used when dealing with mixed types from built-in/library functions, where you have no idea what the actual type is, but when you write the code, you have a reasonable expectation of a set of types and you want to throw if it is unexpected.
My argument is that you might have a set of expected types which includes null, *and* want to throw for other, unexpected, values. If "|null" is special-cased to mean "default to null", there's no way to do that.
Right now, the best way to do that is to simply set a function signature and pass the mixed type to the function to have the engine do it for you
And if you do that, then a value of 'hello' passed to a parameter of type int|null, will throw a TypeError, not give you a null.
As I illustrated in my last e-mail, you can even (since PHP 8.2) have a parameter of type null, and get a TypeError for any other value. That may not be useful, but it's entirely logical.
It makes more sense, from a practical programming point-of-view, to simply return the value given if none of the types match.
This perhaps is a key part of our difference: when I see "int|bool|null", I don't see any "value given", just three built-in types: int, which has a range of values from PHP_INT_MIN to PHP_INT_MAX; bool, which has two possible values "true" and "false"; and null, which has a single possible value "null".
So there are 2**64 + 2 + 1 possible values that meet the constraint, and nothing to specify that one of those is my preferred default if given something unexpected.
Regards, -- Rowan Tommins [IMSoP]