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?
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
On Tue, 3 Nov 2020 at 16:48, Benjamin Morel benjamin.morel@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
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, 3 Nov 2020 at 16:48, Benjamin Morel benjamin.morel@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 isnull
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
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
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é
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
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
.
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
Heya,
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):
$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.
Hey,
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):$accounts->get($receiver) ->addFunds( $accounts->get($sender) ->detractFunds($amount) );
In the above scenario, if the first
$accounts->get()
call returnsnull
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-460441229Safe = 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
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
.
Heya,
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):$accounts->get($receiver) ->addFunds( $accounts->get($sender) ->detractFunds($amount) );
In the above scenario, if the first
$accounts->get()
call returnsnull
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-460441229Safe = crashes when it should crash.
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:
function expensive_function() {
var_dump(__FUNCTION__);
}
$foo = null;
$foo?->baz(expensive_function());
Heya,
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):$accounts->get($receiver) ->addFunds( $accounts->get($sender) ->detractFunds($amount) );
In the above scenario, if the first
$accounts->get()
call returnsnull
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-460441229Safe = crashes when it should crash.
Thus, can you provide any other dangerous example?
But wait!
In your example, funds won't get detracted. If
$accounts->get($receiver)
will returnnull
, then everything insideaddFunds(...)
will not be
executed.
Your example (simplified): https://3v4l.org/38Dk3Another one:
function expensive_function() { var_dump(__FUNCTION__); } $foo = null; $foo?->baz(expensive_function());
Heya,
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):$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-460441229Safe = crashes when it should crash.
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
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:
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
Hey,
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 theobject
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 theobject
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:
interface Accounts
{
/** @throws AccountNotFoundException */
public function get(AccountHolder $id): Account;
}
In following usage:
$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:
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:
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 returnsNullObject<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.
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