Re: NULL Coercion Consistency
| From: | Rowan Tommins | Date: | Sat, 16 Apr 2022 11:17:17 +0000 |
| Subject: | Re: NULL Coercion Consistency | ||
| References: | 1 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-117537@lists.php.net to get a copy of this message | ||
On 8 April 2022 18:34:52 BST, Craig Francis <craig@craigfrancis.co.uk> wrote:
>I've written a new draft RFC to address the NULL coercion problems:
>
>https://wiki.php.net/rfc/null_coercion_consistency
I'm sympathetic to the general problem you're trying to solve, but I'm not convinced
by the argument that this is about consistency, because user defined functions have been
consistently rejecting null for non-nullable parameters since PHP 7.0, and even before that for
arrays and objects. Consistency is a good argument for small changes that eliminate unusual edge
cases, but I think far-reaching changes should be able to stand on their own merits - regardless of
whether it's consistent, do we want the language to work this way?
To me, the main defence of type coercion is that PHP operates in a world full of strings - URLs are
strings, HTTP requests are strings, and a lot of API responses are strings - so making it easy to
work with strings like '42' as numbers makes a lot of sense. It's not clear to me
that this extends to null.
I think a large part of the problem here is that null can mean many different things in different
contexts - "unknown", "not provided", "invalid input",
"default", "not applicable", etc.
These differences are subtle, but lead to different expectations of behaviour:
- Treat null as a specific case with its own meaning, distinct from any other valid value. This is
what explicitly nullable parameters and union types allow.
- Treat null the same as any other out of range value, and raise an error. This is what happens in
user-defined functions in PHP, and in built-in functions expecting non-scalar arguments. Compare
also out of range actions like division by zero.
- Treat null as a special state that propagates through expressions, because any operation with an
unknown input has an unknown output. This is the approach to null taken by SQL, and by IEEE floating
point with NaN. It is also the basis of the ?-> operator, and of things like Optional.map in
Swift.
- Treat null as a generic default value which can be filled in implicitly based on requirements.
This is the interpretation currently taken by internal functions for scalar arguments, and what you
are proposing to make standard. It is also, as you point out, the way PHP treats null in some other
contexts, such as many operators.
Interestingly, one of your examples mentions filter_input, which takes the "propagate"
approach, and htmlspecialchars, which doesn't. It would often be more useful to retain the
information that a value is null than to have it silently converted to an empty string as a
side-effect of some other operation.
Perhaps it would be useful to have some function-call equivalent of the ?-> operator. I'm
not sure what this would look like for normal function calls, but it would be easy to add if we had
a pipe operator, e.g.:
If this was equivalent to htmlspecialchars($foo)
$foo |> htmlspecialchars(...)
Then this could be equivalent to ($foo === null ? null : htmlspecialchars($foo))
$foo ?|> htmlspecialchars(...)
I'm not set against this RFC, but I'm not quite convinced by the case it makes, and think
there may still be other options to explore.
Regards,
--
Rowan Tommins
[IMSoP]