Re: NULL Coercion Consistency

From: Date: Mon, 09 May 2022 23:02:18 +0000
Subject: Re: NULL Coercion Consistency
References: 1 2 3 4  Groups: php.internals 
Request: Send a blank email to internals+get-117707@lists.php.net to get a copy of this message
On 7 May 2022, at 22:11, Jordan LeDoux <jordan.ledoux@gmail.com> wrote: > On Sat, May 7, 2022 at 1:38 AM Craig Francis <craig@craigfrancis.co.uk > <mailto:craig@craigfrancis.co.uk>> wrote: > > Not what I'm going for... but anyway, to get an idea of your position, do you think the > string '15' should be coerced to a number, or should it fatal error as well? e.g. > > $my_number = round($request->get('my_number')); > > > '15' is not an undefined value. > > If a program uses the paradigm that null represents the equivalent of something like > unset($var) Hi Jordan, Some programers can do that, but it's not how everyone sees it. NULL is not the same as unset(), it is a value in itself, and many programmers simply give no thought to the distinction between an Empty String and NULL. e.g. when using the noted frameworks, NULL is provided for user values, and developers will often treat it the same as an Empty String... if $name == '', then show an error saying your name is required; if strlen($name) > 200 then your name is too long, etc. > What exactly would be the purpose of ?int if this RFC was > passed? To pass the value as null instead of 0? I'll talk about int below; but just so I can focus on the nullability part of this question, I'll use ?string... Yes, that's all I'm proposing. For example ?string would not change (it will continue to provide the NULL value to the function), whereas string would coerce NULL to an Empty String, e.g. ``` function accept_nullable_string(?string $my_string) { var_dump($my_string); } function accept_string(string $my_string) { var_dump($my_string); } accept_nullable_string(3); // string(1) "3" accept_nullable_string('2'); // string(1) "2" accept_nullable_string(true); // string(1) "1" accept_nullable_string(false); // string(0) "" accept_nullable_string(NULL); // NULL accept_string(3); // string(1) "3" accept_string('2'); // string(1) "2" accept_string(true); // string(1) "1" accept_string(false); // string(0) "" accept_string(NULL); // Currently TypeError, change to string(0) "" ``` > That's it? Yep, but the point is about avoiding the upgrade problem when internal functions will stop coercing NULL, and instead throw type errors (these are hard to find, and will break code in seemly random places). > What about int|float? Which one would it be coerced to? Good question, Short answer; today, if you pass the string '4' to a function that uses int|float, then it receives int(4): ``` function accept_number(int|float $my_int) { var_dump($my_int); } accept_number('4'); // int(4) ``` Longer answer... First I'll note the documentation says (for string to integer conversion) "If the string is numeric or leading numeric then it will resolve to the corresponding integer value, otherwise it is converted to zero (0)." https://www.php.net/manual/en/language.types.integer.php#language.types.integer.casting <https://www.php.net/manual/en/language.types.integer.php#language.types.integer.casting> But an Empty String is not coerced to int(0) for user defined functions, or arithmetic (e.g. 5 + "" complains after PHP 7.1)... whereas, an empty string is converted to 0 when using intval($var), and (int) $var. While I think this can be a bit harsh, I can see how it might avoid some ambiguity/issues, e.g. $offset = ''; $a = substr('abc', $offset), although I have seen substr('abc', NULL, $length) a few times. If PHP was to throw an exception for NULL to integer coercion with internal functions, while I recognise this would still create a BC break (e.g. round($nullable)), I'd might be fine with it, because an Empty String to Integer is also rejected. That said, and back to your question about int|float, because the string '4' is coerced to int(4), I'd prefer NULL to be coerced to int(0). > This pretty radically changes how typing itself would work in existing user programs. I wouldn't say this is radical, considering it's just removing a single type error (I suspect it's rare for code to rely on NULL coercion triggering an exception)... in contrast, internal functions have always worked with null coercion, and works in other contexts like string concatenation ('A' . $nullable), == comparisons, the print()/echo() functions, etc. > What I'm saying is that for such a radical BC break you need to provide some compelling > justification, and I'm concerned from your replies in this thread and the content of the RFC > that you don't actually understand the extent of a BC-break you're proposing. As noted previously, and the example I've added to the "Backward Incompatible Changes" section, I don't think this is a radical BC break, it's fairly small change designed to make NULL coercion work as documented, in a constant way (all contexts), and most importantly, avoid the upgrade problems for PHP 9.0 (please keep in mind the lack of tooling to help with this, where the only safe and easy way to handle this would be to add NULL coalescing throughout the code base, which is not a good idea). Craig

« previous php.internals (#117707) next »