Nullsafe

php.internals

[ ]

5 years ago
Hello, internals! I am wondering why don't we use ordinary `->` operator with safe null handling? Programmers will be more prone to return null values. And thus, in most of cases `?->` will replace `->`. Why do we need another operator, if we can implement null safe in current operator without BC breaks?

Benjamin Morel

5 years ago
On Tue, 3 Nov 2020 at 17:38, Eugene Sidelnyk <zsidelnik@gmail.com> wrote:
> I am wondering why don't we use ordinary `->` operator with safe null > handling?
Hi, - changing the current behaviour of `->` would be a huge BC break - and if we need another reason, there are many situations where you do want to fail early if the left operand is null, so the current operator is a good fit — Benjamin

Girgias

5 years ago
On Tue, 3 Nov 2020 at 16:48, Benjamin Morel <benjamin.morel@gmail.com> wrote:
> On Tue, 3 Nov 2020 at 17:38, Eugene Sidelnyk <zsidelnik@gmail.com> wrote: > > > I am wondering why don't we use ordinary `->` operator with safe null > > handling? >
Implicit nullable is terrible, moreover I don't see why users should return null values more often. They serve their purpose but most of the time you can use another sane/safe default of the given property type. Moreover, I don't see the issue with another operator as it conveys *clear* intent especially in PHP as everything is `null` by default (might warn but still). *Technically* this isn't a BC break, but it's an enormous change of semantics for something which is worse IMHO. Also the operator has already been voted on, accepted, and implemented. Regards, George P. Banyard

[ ]

5 years ago
About implemented, there were many implemented features, but not accepted. I was very frustrated, when I knew that PHP could have https://wiki.php.net/rfc/userspace_operator_overloading , and it was already implemented. But some people vote down, because they are "narrow-minded" and "have bad preconceptions" and would like to find more things they like about the feature. On Tue, Nov 3, 2020 at 8:33 PM G. P. B. <george.banyard@gmail.com> wrote:

Dan Ackroyd

5 years ago
On Tue, 3 Nov 2020 at 19:36, Eugene Sidelnyk <zsidelnik@gmail.com> wrote:
> But some people vote down, because they are "narrow-minded" and "have bad > preconceptions" and would like to find more things they like about the > feature.
Please don't call people names. People vote based on their own choice, and yes sometimes people disagree on whether something is good or bad. If nothing else, people are unlikely to take the time to explain the basis of their choice to you, if they see that it's likely to result in being called names. cheers Dan Ack

Máté Kocsis

5 years ago
> > But some people vote down, because they are "narrow-minded" and "have bad > > preconceptions" and would like to find more things they like about the > > feature. >
I think these words are quoted from a comment of mine on the operator overloading RFC (or a surprising coincidence). This was a mistake on my part, sorry for that. Regards, Máté

Josh Bruce

5 years ago
> Implicit nullable is terrible, moreover I don't see why users should return > null values more often. > They serve their purpose but most of the time you can use another sane/safe > default of the given > property type. >
Agreed. I’m not overly opinionated about much, but null as a default habit is one of them. I want the use of null and workarounds to feel ugly and dirty, because they are - imho. (The billion dollar mistake.) Absolute last resort. Also agreed on explicit communication of intent. If the operator flipped is want another operator to disallow null...at which point the world has not changed, just perceived default and desirable methods. Cheers, Josh

Claude Pache

5 years ago
> Le 3 nov. 2020 à 17:38, Eugene Sidelnyk <zsidelnik@gmail.com> a écrit : > > Hello, internals! > I am wondering why don't we use ordinary `->` operator with safe null > handling? Programmers will be more prone to return null values. And thus, > in most of cases `?->` will replace `->`. > Why do we need another operator, if we can implement null safe in current > operator without BC breaks?
Hi, In a parallel world where people never commit bugs, yes, this is reasonable semantics. However, it happens more than once that a null value occurs by accident where an object is expected by the programmer. In this case, I very much prefer to have a Warning or an Error, typically handled by an error handler which sends a bug report to the developer, then triggers an emergency stop. That makes it much easier to locate and fix bugs. Of course, this debuggability facility have to be balanced with the inconvenience to having to write more code in cases where a null value is expected. Having to write “?->” instead of “->” in those cases is a minimal inconvenience. Here, I mean “minimal” in a very literal way: exactly one character of difference. Also, there is the following benefit: When I write “->”, I am stating that the LHS value should never be null; when I write “?->”, I am stating that I expect that the LHS is sometimes null. Thus, my code is documented in a very concise way (between zero and one character), which is an aid for any future reader (including myself). Aside: The name “nullsafe” is misleading. The operator is not about safety, it is about handling null values in a concise way. In fact, in some circumstances, sweeping an unexpected null value under the rug may be totally unsafe. —Claude

[ ]

5 years ago
However, `null` value is not a good way to use a default value instead of an object. When it comes to polymorphism, we would like to avoid null checks from being all around. The same goes not only for `->`, but also return / argument type-hintings. Why do we even need to know that object can be null? Null is just a special case (there may be other special cases). Does my customer code need to know about all special cases? Apparently not. But still, we create another operator to handle this case. It is ok when it comes to third-party libraries we don't have control over and sometimes it could return null. Yet this approach doesn't solve the primary problem. Polymorphism. You can argue that `null` can be used to indicate that something was not found. But this is not the case for returning null. Here an exception should be thrown. When I would like to return some default value of an object, it can't just be null (because it will pile up the code with ? signs). I need to create a separate NullObject, which implements a particular interface used at runtime, and return it's instance. If an interface declares a returning object, then I have to write even more boilerplate code for those objects. It is all for such thing as the default "null" value for an object. IMHO, simple things should be done in a simple way. What I currently see is that to do a simple thing, I need to create `N` `null`-classes, whose methods will eventually return `""` or `0` or `0.0` or `false` or `[]` - generally speaking `null`. On Tue, Nov 3, 2020 at 9:56 PM Claude Pache <claude.pache@gmail.com> wrote:

Marco Pivetta

5 years ago
Heya, On Tue, Nov 3, 2020, 17:38 Eugene Sidelnyk <zsidelnik@gmail.com> wrote:
> Hello, internals! > I am wondering why don't we use ordinary `->` operator with safe null > handling? Programmers will be more prone to return null values. And thus, > in most of cases `?->` will replace `->`. > Why do we need another operator, if we can implement null safe in current > operator without BC breaks? >
Overall, "null safe" can be very dangerous if made the default. Here's a scenario where I'd never want "null safe" behaviour (which does anything but introducing safety): ```php $accounts->get($receiver) ->addFunds( $accounts->get($sender) ->detractFunds($amount) ); ``` In the above scenario, if the first `$accounts->get()` call returns `null` for any reason, you may actually destroy money (funds detracted, but never added to another account). The example is simplistic, but it shows that "null safe" is everything but "safe", and it must instead be used only where it absolutely makes sense to suppress null reference errors. Similar behaviour can be observed around cast operators, which are too lax for most businesses logic: https://github.com/ShittySoft/symfony-live-berlin-2018-doctrine-tutorial/pull/3#issuecomment-460441229 Safe = crashes when it should crash.

Ilija Tovilo

5 years ago
Hey, On Tue, Nov 3, 2020 at 9:11 PM Marco Pivetta <ocramius@gmail.com> wrote:
> Overall, "null safe" can be very dangerous if made the default. > > Here's a scenario where I'd never want "null safe" behaviour (which does > anything but introducing safety): > > ```php > $accounts->get($receiver) > ->addFunds( > $accounts->get($sender) > ->detractFunds($amount) > ); > ``` > > In the above scenario, if the first `$accounts->get()` call returns `null` > for any reason, you may actually destroy money (funds detracted, but never > added to another account). > > The example is simplistic, but it shows that "null safe" is everything but > "safe", and it must instead be used only where it absolutely makes sense to > suppress null reference errors. > > Similar behaviour can be observed around cast operators, which are too lax > for most businesses logic: > https://github.com/ShittySoft/symfony-live-berlin-2018-doctrine-tutorial/pull/3#issuecomment-460441229 > > Safe = crashes when it should crash.
I agree. The goal here is not to never encounter a crash. The goal is to make dealing with known potential null values easier. Crashing is the safer behavior when something happens you might not have taken into account. Honestly, in hindsight "nullsafe" might not have been the best term. I adopted the term from the previous RFC. There are actually quite a few other languages that use the word "safe" for this feature. https://en.wikipedia.org/wiki/Safe_navigation_operator The most obvious alternative would be optional chaining. I'd suggest renaming it but I don't think that's viable at this point because those token names might already be used in various tools and extensions. Ilija

[ ]

5 years ago
Hm, yes. If the `get()` method can return `null`, then it is a problem. Currently `null` is abused to indicate that value was not found. `NotFoundException` should be thrown instead. But.. yeah, we can't just use `nullsafe` by default because of abuse of `null`. On Tue, Nov 3, 2020 at 10:11 PM Marco Pivetta <ocramius@gmail.com> wrote:

[ ]

5 years ago
But wait! In your example, funds won't get detracted. If `$accounts->get($receiver)` will return `null`, then everything inside `addFunds(...)` will not be executed. Your example (simplified): https://3v4l.org/38Dk3 Another one: ```php function expensive_function() { var_dump(__FUNCTION__); } $foo = null; $foo?->baz(expensive_function()); ``` On Tue, Nov 3, 2020 at 10:11 PM Marco Pivetta <ocramius@gmail.com> wrote:

[ ]

5 years ago
Thus, can you provide any other dangerous example? On Wed, Nov 4, 2020, 6:59 AM Eugene Sidelnyk <zsidelnik@gmail.com> wrote:

Christian Schneider

5 years ago
Am 04.11.2020 um 19:39 schrieb Eugene Sidelnyk <zsidelnik@gmail.com>:
> Thus, can you provide any other dangerous example?
I think at this point you could have realised that it is a) a BC break (code which has thrown an exception before now wouldn't) b) not finding any love among the community here. Possibly for a reason? If you really want you could put an RFC together but I'm pretty sure that it wouldn't be accepted. And now for your dangerous example: try { $foo = null; $foo->bar(); deletel_all_files_on_machine(); } catch (Error $dummy) {} Regards, - Chris

[ ]

5 years ago
Yeah... Creating null was a huge mistake. Now it is probably too late to fix that (maybe some new language can introduce that). But what do you think about introducing special class `NullObject`? On Wed, Nov 4, 2020, 9:32 PM Christian Schneider <cschneid@cschneid.com> wrote:

Marco Pivetta

5 years ago
Hey, On Wed, Nov 4, 2020, 20:39 Eugene Sidelnyk <zsidelnik@gmail.com> wrote:
> Yeah... Creating null was a huge mistake. > Now it is probably too late to fix that (maybe some new language can > introduce that). > >
Y'all confusing Java's `null` (billion dollar mistake) with PHP's `null`. * In Java, `null|object` passes the `object` property and parameter type declaration, but crashes on `.` (`->` operator in PHP). That means that the type system is fundamentally flawed. * In PHP, `null|object` does not pass the `object` property and parameter type declaration, and crashes on `->` The PHP `T|null` type is almost equivalent to `data Maybe T = Just T | Nothing` from typed functional languages, and does not present the same "billion dollar mistake" flaws of Java. It is also an accurate and sufficient representation of "absence of value". That's why I linked the `toString()` vs `(string)` example in https://github.com/ShittySoft/symfony-live-berlin-2018-doctrine-tutorial/pull/3#issuecomment-460441229 Also, this was previously discussed in a similar way in https://externals.io/message/108369#108386 But what do you think about introducing special class `NullObject`?
>
You can really only apply the null object pattern (you can use the one from https://github.com/Ocramius/ProxyManager/blob/2.9.1/docs/null-object.md, which is relatively type-safe) when interactions with such an object leading to no effect are valid. In the example I wrote above: Let's assume following repository signature: ```php interface Accounts { /** @throws AccountNotFoundException */ public function get(AccountHolder $id): Account; } ``` In following usage: ```php $accounts->get($receiver) ->addFunds( $accounts->get($sender) ->detractFunds($amount) ); ``` Assuming `->` were capable of operating with `object|null` (OP proposal), if (by accident) somebody decided to change this signature: ```diff interface Accounts { - /** @throws AccountNotFoundException */ - public function get(AccountHolder $id): Account; + public function get(AccountHolder $id): ?Account; } ``` In this scenario, what can happen is that money disappears, as I've written above:
> In the above scenario, if the first `$accounts->get()` call returns
`null` for any reason, you may actually destroy money (funds detracted, but never added to another account). The same would happen if a `NullObject` were used: ```diff interface Accounts { - /** @throws AccountNotFoundException */ - public function get(AccountHolder $id): Account; + public function get(AccountHolder $id): NullObject<Account>|Account; } ``` Same scenario (slight variation):
> In the above scenario, if the first `$accounts->get()` call returns `
NullObject<Account> ` for any reason, you may actually destroy money (funds detracted, but never added to another account). I would say that embracing nullability **in a type-safe manner** (please use https://psalm.dev/ or https://phpstan.org/) leads to proper usage of `null`, where it correctly indicates cases where no value has been produced/found/calculated/etc. Null pointer exceptions are trivially avoided by using any of these relatively stable and well adopted static analysis tools, and we don't really need to fight windmills at all.

Josh Bruce

5 years ago
> On Nov 4, 2020, at 1:38 PM, Eugene Sidelnyk <zsidelnik@gmail.com> wrote: > > Yeah... Creating null was a huge mistake. > Now it is probably too late to fix that (maybe some new language can > introduce that). > > But what do you think about introducing special class `NullObject`?
I would rather let an instance be “empty” than to have a unique, separate thing that always represents nothing. To be fair, I don’t know the specific NullObject implementation here - based on presumption - I would intentionally be creating something that represents nothing. See also this RFC: https://wiki.php.net/rfc/objects-can-be-falsifiable <https://wiki.php.net/rfc/objects-can-be-falsifiable> - sounds like this thread might bring more supporters to that table/discussion. Cheers, Josh