Hey internals! A useful feature that Hack picked up in the last few months are "nullsafe calls", a way of propagating failure forward in a series of chained method calls to the end of the whole computation, getting rid of a lot of the boilerplate in the middle. I think the feature would be a good one for PHP as well, so I'm submitting this RFC to add it -- you can see the RFC itself for a full discussion of the motivation for the feature, as well as the feature itself:
https://wiki.php.net/rfc/nullsafe_calls
Josh Watzman
Hey internals! A useful feature that Hack picked up in the last few months are "nullsafe calls", a way of propagating failure forward in a series of chained method calls to the end of the whole computation, getting rid of a lot of the boilerplate in the middle. I think the feature would be a good one for PHP as well, so I'm submitting this RFC to add it -- you can see the RFC itself for a full discussion of the motivation for the feature, as well as the feature itself:
Hi!
In a way, this would complement the ?? null coalescing operator we’re adding. :)
Two thoughts, though:
-
Wouldn’t it be useful to also have this for properties? If you’re going down a long list of property accesses, $foo?->bar?->qux is probably nicer than if ($foo !==
NULL
&& $foo->bar !== NULL) { $foo->bar->qux; … } - on the other hand, the ?? operator partly covers this and even handles arrays, as you can do $foo->bar[‘elePHPant']->qux[‘boop’] ??NULL
already. We also couldn’t add something like ?-> for arrays, as the ?[] syntax is ambiguous (looks like ternary with short array syntax). -
It’d probably be better if you made a language specification patch before, not after, the RFC is accepted. Having to specify the syntax and semantics formally can make what the operator does clearer and help to spot issues. Plus, going forward, the language specification should not be an afterthought.
Otherwise, though, I’m in favour of this change.
Thanks!
--
Andrea Faulds
http://ajf.me/
Hey internals! A useful feature that Hack picked up in the last few months are "nullsafe calls", a way of propagating failure forward in a series of chained method calls to the end of the whole computation, getting rid of a lot of the boilerplate in the middle. I think the feature would be a good one for PHP as well, so I'm submitting this RFC to add it -- you can see the RFC itself for a full discussion of the motivation for the feature, as well as the feature itself:
Hi!
In a way, this would complement the ?? null coalescing operator we’re adding. :)
Two thoughts, though:
- Wouldn’t it be useful to also have this for properties? If you’re going down a long list of property accesses, $foo?->bar?->qux is probably nicer than if ($foo !==
NULL
&& $foo->bar !== NULL) { $foo->bar->qux; … } - on the other hand, the ?? operator partly covers this and even handles arrays, as you can do $foo->bar[‘elePHPant']->qux[‘boop’] ??NULL
already. We also couldn’t add something like ?-> for arrays, as the ?[] syntax is ambiguous (looks like ternary with short array syntax).
Property access could be useful as well, I just didn't want to deal with it for the first version of the RFC. It's actually already listed in "future scope". :) If this method call portion goes well, I'd be willing to submit an RFC/implementation for that as well, but I wanted to get method calls done first. (It's also somewhat less clearly useful than method calls, especially with '??' as you point out, which is why I went with method calls first :))
- It’d probably be better if you made a language specification patch before, not after, the RFC is accepted. Having to specify the syntax and semantics formally can make what the operator does clearer and help to spot issues. Plus, going forward, the language specification should not be an afterthought.
Definitely not an afterthought. I just want to get through all of the "not clear if it's going to work" bits -- first, the feasibility of the actual implementation, then whether the RFC got good reception on internals. I'll make sure to have a spec diff before the RFC goes up for a vote -- does that sound good?
Otherwise, though, I’m in favour of this change.
Thanks for the feedback!
Josh
Hi!
- It’d probably be better if you made a language specification patch before, not after, the RFC is accepted. Having to specify the syntax and semantics formally can make what the operator does clearer and help to spot issues. Plus, going forward, the language specification should not be an afterthought.
Definitely not an afterthought. I just want to get through all of the "not clear if it's going to work" bits -- first, the feasibility of the actual implementation, then whether the RFC got good reception on internals. I'll make sure to have a spec diff before the RFC goes up for a vote -- does that sound good?
Yes, that sounds good, thanks!
Andrea Faulds
http://ajf.me/
Hi,
-----Ursprüngliche Nachricht-----
Von: Josh Watzman [mailto:jwatzman@fb.com]
Gesendet: Mittwoch, 10. Dezember 2014 00:08
An: PHP internals
Betreff: [PHP-DEV] [RFC] Nullsafe callsHey internals! A useful feature that Hack picked up in the last few months are "nullsafe calls", a way of propagating
failure
forward in a series of chained method calls to the end of the whole computation, getting rid of a lot of the
boilerplate in the
middle. I think the feature would be a good one for PHP as well, so I'm submitting this RFC to add it -- you can see
the RFC
itself for a full discussion of the motivation for the feature, as well as the feature itself:https://wiki.php.net/rfc/nullsafe_calls
Josh Watzman
--
First of all, I like the RFC, I think as well that it is a useful feature for PHP and I already have it on my wish list
;)
Yet, either I misunderstand the section about short circuiting or I propose to change the behaviour.
"If $obj is null, when $obj?->foo(..) executes, the arguments will still be evaluated."
IMO that is wrong. It should not evaluate the arguments since the nullsafe operator (sometimes called "safe navigation"
operator in other languages - which is the better wording IMO, maybe change it? But that is just a detail) is just
syntactic sugar for making the call only if $bj is not null. So the following:
$obj = null;
$obj?->foo($a, $b);
should be the same as
$obj = null;
If($obj !== null){
$obj->foo($a, $b);
}
hence PHP will not complain that $a and $b where not defined.
-----Ursprüngliche Nachricht-----
Von: Robert Stoll [mailto:php@tutteli.ch]
Gesendet: Mittwoch, 10. Dezember 2014 17:17
An: 'Josh Watzman'; 'PHP internals'
Betreff: AW: [PHP-DEV] [RFC] Nullsafe callsHi,
-----Ursprüngliche Nachricht-----
Von: Josh Watzman [mailto:jwatzman@fb.com]
Gesendet: Mittwoch, 10. Dezember 2014 00:08
An: PHP internals
Betreff: [PHP-DEV] [RFC] Nullsafe callsHey internals! A useful feature that Hack picked up in the last few
months are "nullsafe calls", a way of propagating
failure
forward in a series of chained method calls to the end of the whole
computation, getting rid of a lot of the
boilerplate in the
middle. I think the feature would be a good one for PHP as well, so
I'm submitting this RFC to add it -- you can see
the RFC
itself for a full discussion of the motivation for the feature, as well as the feature itself:https://wiki.php.net/rfc/nullsafe_calls
Josh Watzman
--
To unsubscribe,
visit: http://www.php.net/unsub.phpFirst of all, I like the RFC, I think as well that it is a useful feature for PHP and I already have it on my wish
list
;)Yet, either I misunderstand the section about short circuiting or I propose to change the behaviour.
"If $obj is null, when $obj?->foo(..) executes, the arguments will still be evaluated."
IMO that is wrong. It should not evaluate the arguments since the nullsafe operator (sometimes called "safe
navigation"
operator in other languages - which is the better wording IMO, maybe change it? But that is just a detail) is just
syntactic
sugar for making the call only if $bj is not null. So the following:$obj = null;
$obj?->foo($a, $b);should be the same as
$obj = null;
If($obj !== null){
$obj->foo($a, $b);
}hence PHP will not complain that $a and $b where not defined.
--
Appendum: I somehow skipped the section "Prior Art". So I see, that the different behaviour was on purpose. I do not
think it is a clever idea to implement a common concept differently. In this sense -1
Changing the RFC and implementing it the same way others do +1
-----Ursprüngliche Nachricht-----
Von: Robert Stoll [mailto:php@tutteli.ch]
Gesendet: Mittwoch, 10. Dezember 2014 17:20
An: 'Josh Watzman'; 'PHP internals'
Betreff: AW: [PHP-DEV] [RFC] Nullsafe calls-----Ursprüngliche Nachricht-----
Von: Robert Stoll [mailto:php@tutteli.ch]
Gesendet: Mittwoch, 10. Dezember 2014 17:17
An: 'Josh Watzman'; 'PHP internals'
Betreff: AW: [PHP-DEV] [RFC] Nullsafe callsHi,
-----Ursprüngliche Nachricht-----
Von: Josh Watzman [mailto:jwatzman@fb.com]
Gesendet: Mittwoch, 10. Dezember 2014 00:08
An: PHP internals
Betreff: [PHP-DEV] [RFC] Nullsafe callsHey internals! A useful feature that Hack picked up in the last few
months are "nullsafe calls", a way of propagating
failure
forward in a series of chained method calls to the end of the whole
computation, getting rid of a lot of the
boilerplate in the
middle. I think the feature would be a good one for PHP as well, so
I'm submitting this RFC to add it -- you can see
the RFC
itself for a full discussion of the motivation for the feature, as well as the feature itself:https://wiki.php.net/rfc/nullsafe_calls
Josh Watzman
--
To unsubscribe,
visit: http://www.php.net/unsub.phpFirst of all, I like the RFC, I think as well that it is a useful
feature for PHP and I already have it on my wish
list
;)Yet, either I misunderstand the section about short circuiting or I propose to change the behaviour.
"If $obj is null, when $obj?->foo(..) executes, the arguments will still be evaluated."
IMO that is wrong. It should not evaluate the arguments since the
nullsafe operator (sometimes called "safe
navigation"
operator in other languages - which is the better wording IMO, maybe
change it? But that is just a detail) is just
syntactic
sugar for making the call only if $bj is not null. So the following:$obj = null;
$obj?->foo($a, $b);should be the same as
$obj = null;
If($obj !== null){
$obj->foo($a, $b);
}hence PHP will not complain that $a and $b where not defined.
--
To unsubscribe,
visit: http://www.php.net/unsub.phpAppendum: I somehow skipped the section "Prior Art". So I see, that the different behaviour was on purpose. I do not
think it is a clever idea to implement a common concept differently. In this sense -1 Changing the RFC and
implementing it
the same way others do +1--
Appendum 2:
hm... my example was wrong as well, I was too quick. It should have been like this of course:
$obj = null;
$obj !== null ? $obj->foo($a, $b) : null;
First of all, I like the RFC, I think as well that it is a useful feature for PHP and I already have it on my wish list
;)Yet, either I misunderstand the section about short circuiting or I propose to change the behaviour.
"If $obj is null, when $obj?->foo(..) executes, the arguments will still be evaluated."
IMO that is wrong. It should not evaluate the arguments since the nullsafe operator (sometimes called "safe navigation"
operator in other languages - which is the better wording IMO, maybe change it? But that is just a detail) is just
syntactic sugar for making the call only if $bj is not null. So the following:[...]
Take a look at the "short circuit" section where I specifically address this. It was done to make it easier to reason about in a side-effect-ful language like PHP. With this behavior, when you see
$x?->f(g(), h())
you don't have to worry about thinking whether g() and h() will actually be evaluated or not -- only the call to f() itself, which is explicitly marked as nullsafe (i.e., might not happen). It also comes down to what you mean by "not making the method call when $x is null" -- we in fact don't make the method call, but we still prepare everything as if we were going to, and just decide not to do it at the last moment.
I feel pretty strongly that this is the correct behavior. It may sound strange when talking about isolated examples like this, but in the real-world code I've seen, it is the least confusing. (I'll see if I can dig up a better example instead of just citing nebulous "real-world code" ;))
Josh
-----Ursprüngliche Nachricht-----
Von: Josh Watzman [mailto:jwatzman@fb.com]
Gesendet: Mittwoch, 10. Dezember 2014 19:43
An: Robert Stoll
Cc: PHP internals
Betreff: Re: [PHP-DEV] [RFC] Nullsafe callsFirst of all, I like the RFC, I think as well that it is a useful
feature for PHP and I already have it on my wish list
;)Yet, either I misunderstand the section about short circuiting or I propose to change the behaviour.
"If $obj is null, when $obj?->foo(..) executes, the arguments will still be evaluated."
IMO that is wrong. It should not evaluate the arguments since the nullsafe operator (sometimes called "safe
navigation"
operator in other languages - which is the better wording IMO, maybe
change it? But that is just a detail) is just syntactic sugar for making the call only if $bj is not null. So the
following:[...]
Take a look at the "short circuit" section where I specifically address this. It was done to make it easier to reason
about in a
side-effect-ful language like PHP. With this behavior, when you see$x?->f(g(), h())
you don't have to worry about thinking whether g() and h() will actually be evaluated or not -- only the call to f()
itself,
which is explicitly marked as nullsafe (i.e., might not happen). It also comes down to what you mean by "not making
the
method call when $x is null" -- we in fact don't make the method call, but we still prepare everything as if we were
going to,
and just decide not to do it at the last moment.I feel pretty strongly that this is the correct behavior. It may sound strange when talking about isolated examples
like this,
but in the real-world code I've seen, it is the least confusing. (I'll see if I can dig up a better example instead of
just citing
nebulous "real-world code" ;))Josh
--
I stick with it, evaluating it does not make sense IMO. If I want to execute it in any case then I would do something
like this currently
$g = g();
$h = h();
if($x !== null){
$x->foo($g, $h)->bar(baz());
}
And with the ?-> operator:
$g = g();
$h = h();
$x?->foo($g, $h)?->bar(baz());
Where I do not expect that baz() is called. You wrote it will be confusing for the user if the arguments would not be
evaluated. I do not think so, telling them it is a short form for ($x !== null ? $x->foo() : null) is very easy and
straight forward for the user to pick up. Therefore, please dig up a better example. Right now, I think it is a good
idea in general to add a nullsafe operator to PHP but not only in the way mentioned.
I stick with it, evaluating it does not make sense IMO. If I want to execute it in any case then I would do something
like this currently$g = g();
$h = h();
if($x !== null){
$x->foo($g, $h)->bar(baz());
}And with the ?-> operator:
$g = g();
$h = h();
$x?->foo($g, $h)?->bar(baz());Where I do not expect that baz() is called.
You can make exactly the opposite argument too, though. I do expect that baz() would be called, and I can always explicitly write it the other way if that happens to not be what I want at some point.
You wrote it will be confusing for the user if the arguments would not be
evaluated. I do not think so, telling them it is a short form for ($x !== null ? $x->foo() : null) is very easy and
straight forward for the user to pick up.
I think it's confusing the other way, and "instead of raising a fatal for calling a method on null, it returns null" is just as simple of an explanation.
Therefore, please dig up a better example.
Yeah, gonna go do this now, had a busy morning :)
All of that said: it's not clear to me that this actually matters that much. I wasn't able to quickly find an example because the vast majority of the Hack code I have that uses this operator isn't actually affected one way or the other.
Josh
Hi!
I think it's confusing the other way, and "instead of raising a fatal
for calling a method on null, it returns null" is just as simple of
an explanation.
What is confusing in "no call is made, so nothing is evaluated" or in
"as soon as we see null, we stop and return null"? I can not believe
anybody smart enough to use a computer could be genuinely confused by this.
All of that said: it's not clear to me that this actually matters
that much. I wasn't able to quickly find an example because the vast
I think it does matter, and weird magic should not be a part of the
language.
--
Stas Malyshev
smalyshev@gmail.com
Hi!
Take a look at the "short circuit" section where I specifically
address this. It was done to make it easier to reason about in a
side-effect-ful language like PHP. With this behavior, when you see$x?->f(g(), h())
I think this is weird magic behavior which should not be part of PHP. If
$x is null, there's no object to call f on, and as such f() call is not
executed. However, if argument still be evaluated even though the call
is never done, this makes no sense at all - why evaluate the arguments
if there's nothing to call? Moreover, what if g() and h() actually use
$x assuming - quite right, since we're calling the method on it - that
$x is not null?
Moreover, having code depending on side effect of function call which is
not part of the call itself is extremely bad style (i.e., what happens
if g() throws exception and h() is never called?) and should not be
encouraged.
-- we in fact don't make the method call, but we still prepare
everything as if we were going to, and just decide not to do it at
the last moment.
Which, again, makes little sense - if we know there would be no call,
why prepare everything and decide at the last moment? It's like you know
your flight was cancelled, but you still drive to the airport, pass
security, give the baggage to the airline, show up at the gate, wait
until the last moment and then - surpries! it's still cancelled! - so
you take your baggage back and drive back home. Why would anybody do that?
I feel pretty strongly that this is the correct behavior. It may
sound strange when talking about isolated examples like this, but in
It does sound strange.
the real-world code I've seen, it is the least confusing. (I'll see
Which real-world code you are talking about? Examples please.
--
Stas Malyshev
smalyshev@gmail.com
the real-world code I've seen, it is the least confusing. (I'll see
Which real-world code you are talking about? Examples please.
I'm having trouble digging any up -- FB's codebase has >10k occurrences of this feature and I have yet to find a single one where the short circuit matters or not -- so maybe my recollection and intuition here are just wrong. I'll keep digging and thinking about this. Not convinced yet, but starting to feel less strongly about it.
Josh
the real-world code I've seen, it is the least confusing. (I'll see
Which real-world code you are talking about? Examples please.
I'm having trouble digging any up -- FB's codebase has >10k occurrences of this feature and I have yet to find a single one where the short circuit matters or not -- so maybe my recollection and intuition here are just wrong. I'll keep digging and thinking about this. Not convinced yet, but starting to feel less strongly about it.
I've moved the RFC back to draft while I think about this more. I've very quickly summarized what turns out to be at least three possibilities for this behavior in the "open issues" section of the RFC. I need to think through those ideas more, as well as investigate implementation feasibility, to see what makes sense, and will resubmit for further discussion once I've done that. (No sense in deciding one particular behavior is right and it turns out to be basically impossible to implement!)
Thanks for the comments everyone!
Josh Watzman
Hey internals! A useful feature that Hack picked up in the last few months are "nullsafe calls", a way of propagating failure forward in a series of chained method calls to the end of the whole computation, getting rid of a lot of the boilerplate in the middle. I think the feature would be a good one for PHP as well, so I'm submitting this RFC to add it -- you can see the RFC itself for a full discussion of the motivation for the feature, as well as the feature itself:
Hi again,
I was in favour of this, but upon further thought, I’m not sure I am. I remember I also initially liked it in Hack and then lost interest.
First, how is this substantially different from catching an exception? With Nikita’s Exceptions in the Engine RFC (https://wiki.php.net/rfc/engine_exceptions_for_php7), something like this would be possible:
try {
return $foo->bar()->qux()->elePHPant()->dance();
} catch (EngineException $e) {
return NULL;
}
That would essentially do the same as what you’re proposing:
return $foo?->bar()?->qux()?->elePHPant()?->dance();
Are there many instances where there’d be a substantial benefit to using ?-> versus catching an exception?
Second, not short-circuiting is rather unintuitive. I think most people would expect it to short-circuit. Note that ?? and isset() short-circuit, so this would be inconsistent with existing NULL-checking operators. So why, then, are you proposing that it should not short-circuit? Is there some obscure case that this makes easier? If anything, I’d expect that short-circuiting is the more useful behaviour. For example, take the following hypothetical line of code:
$elePHPantParty->danceClub?->addToDanceFloor($elePHPantPool->remove());
If we have the short-circuiting behaviour, then if $elePHPantParty->danceClub is NULL, ->addToDanceFloor isn’t called and an ElePHPant isn’t removed from $elePHPantPool. On the other hand, if ->danceClub isn’t NULL, ->addToDanceFloor is called and an ElePHPant will be removed from $elePHPantPool.
With the non-short-circuiting behaviour, this code would have to be longer, and you couldn’t use ?-> here. Instead, you’d have to write this:
if ($elePHPantParty->danceClub !== NULL) {
$elePHPantParty->danceClub->addToDanceFloor($elePHPantPool->remove());
}
It is, admittedly, a hypothetical scenario, but it does demonstrate why having it not short-circuit is less useful. Are there any examples where short-circuiting is a problem?
Third, as has already been mentioned, this doesn’t support ?-> for properties, which is both unintuitive (people will reasonably expect it to work) and makes it a lot less useful, because you won’t always be dealing with method calls. While $foo?->bar()?->foobar() might work, what about something like $foo?->bar?->foobar(), where one of the links in the chain is a property? This is not at all an unlikely scenario.
Finally, this may encourage bad code. As someone helpfully pointed out in the reddit discussion (http://redd.it/2ot15u), this encourages breaking the Law of Demeter, i.e. that objects should only talk to their immediate friends. Usually, long chains of method calls across multiple objects are not a good idea, yet the main benefit of this RFC is to reduce the boilerplate needed for that scenario.
I am only one voice in this discussion, but I personally don’t really like this RFC.
Thanks!
Andrea Faulds
http://ajf.me/
Andrea Faulds wrote on 10/12/2014 17:19:
Finally, this may encourage bad code. As someone helpfully pointed out in the reddit discussion (http://redd.it/2ot15u), this encourages breaking the Law of Demeter, i.e. that objects should only talk to their immediate friends. Usually, long chains of method calls across multiple objects are not a good idea, yet the main benefit of this RFC is to reduce the boilerplate needed for that scenario.
It's an interesting thought, but reading the Wikipedia article, I see
that that "Law" has acknowledged downsides, and so code breaking it is
not universally acknowledged as "bad". I actually think it's a better
argument against chaining property access (which implies deep knowledge
of structures) than against chaining methods (which may be completely
abstract actions unrelated to structure).
In particular, long method chains are commonly associated with "fluent
interfaces", which can be a very effective way of creating a
Domain-Specific Language in an OOP system - database query builders, for
instance, tend to use this approach. It may not even involve exposing
additional objects at all, just "return $this" at the end of a mutator.
On the other hand, methods designed to be used like that should have
strong return contracts, and throw exceptions themselves on error, so
neither engine exceptions nor an extra operator would be necessary if
the underlying library was written well.
I'm kind of on the fence as to whether this is the right solution to the
problem.
--
Rowan Collins
[IMSoP]
Hi again,
I was in favour of this, but upon further thought, I’m not sure I am. I remember I also initially liked it in Hack and then lost interest.
First, how is this substantially different from catching an exception? With Nikita’s Exceptions in the Engine RFC, something like this would be possible:
try {
return $foo->bar()->qux()->elePHPant()->dance();
} catch (EngineException $e) {
return NULL;
}That would essentially do the same as what you’re proposing:
return $foo?->bar()?->qux()?->elePHPant()?->dance();
Are there many instances where there’d be a substantial benefit to using ?-> versus catching an exception?
It's an API decision up to the implementors of the object. If you like using exceptions for every failure, then go for it, this syntax won't cost you anything or get in your way :)
However, for a lot of failures, I don't feel that exceptions are appropriate. I tend to only use them for exceptional behavior -- usually, some failure that needs to be propagated up a few levels up the stack, to an appropriate error boundary. This doesn't necessarily mean a completely unrecoverable error, but it's locally unrecoverable. For things that are completely recoverable, returning null might be more appropriate and more lightweight, and then this nullsafe operator would be useful at that point.
Let me talk about a specific example to illustrate the difference, loading the User object in the context of FB news feed. Privacy checks are built in to the User object, so you can't get a User you can't see, but how you want to deal with that failure depends on the callsite. User actually has two functions, one which returns null if you can't see the User, and the other which throws an exception. Suppose you are loading a news feed story, and you want to display the "actor" of the story (the person whose name appears at the top of the story). You'll want to use the "throw exception" variant there -- if you can't see the actor, you can't display the story at all, and so you should throw an exception. An error boundary in the news feed infra code is what catches the exception, several levels up, and just removes that single story from feed, and moves on. It's locally unrecoverable, though globally we can still load feed. In the other direction, if you've got most of the story loaded and you just want the list of Users who have "liked" the post, you probably want to use the returns-null version, and then use the ?-> operator to get the name of that User. It's completely locally recoverable, some User you can't see just gets left off the list of likers, great. We don't want to abort any operation at all, we can keep going; exceptions are not really the appropriate tool to use here.
Maybe you disagree with the above, and the use of exceptions -- that's fine, I can totally buy arguments that the above isn't the way you or others want to work. But not everyone does -- I at least think it makes sense to work the way I've described, and there are a lot of points on that spectrum, and this new operator makes it easy to operate at different points there.
Second, not short-circuiting is rather unintuitive. I think most people would expect it to short-circuit. Note that ?? and isset() short-circuit, so this would be inconsistent with existing NULL-checking operators. So why, then, are you proposing that it should not short-circuit? Is there some obscure case that this makes easier? If anything, I’d expect that short-circuiting is the more useful behaviour. For example, take the following hypothetical line of code:
$elePHPantParty->danceClub?->addToDanceFloor($elePHPantPool->remove());
If we have the short-circuiting behaviour, then if $elePHPantParty->danceClub is NULL, ->addToDanceFloor isn’t called and an ElePHPant isn’t removed from $elePHPantPool. On the other hand, if ->danceClub isn’t NULL, ->addToDanceFloor is called and an ElePHPant will be removed from $elePHPantPool.
With the non-short-circuiting behaviour, this code would have to be longer, and you couldn’t use ?-> here. Instead, you’d have to write this:
if ($elePHPantParty->danceClub !== NULL) {
$elePHPantParty->danceClub->addToDanceFloor($elePHPantPool->remove());
}It is, admittedly, a hypothetical scenario, but it does demonstrate why having it not short-circuit is less useful. Are there any examples where short-circuiting is a problem?
I think the short-circuiting behavior is the most intuitive, since it looks like a method call, and evaluating arguments is what you do to prepare for a method call -- and this RFC just makes the method call not actually happen at the last moment. That said, although I feel pretty strongly about this, I also didn't find a good example where it mattered either way when I went looking at existing usages in Hack, and your example is indeed an interesting one. Let me spend some more time looking for examples and get back to you.
Third, as has already been mentioned, this doesn’t support ?-> for properties, which is both unintuitive (people will reasonably expect it to work) and makes it a lot less useful, because you won’t always be dealing with method calls. While $foo?->bar()?->foobar() might work, what about something like $foo?->bar?->foobar(), where one of the links in the chain is a property? This is not at all an unlikely scenario.
If internals thinks this is a feature that would make or break the RFC, I'd be willing to add it. I just figured that something smaller would be easier to propose, implement, etc etc for my first RFC :)
Finally, this may encourage bad code. As someone helpfully pointed out in the reddit discussion, this encourages breaking the Law of Demeter, i.e. that objects should only talk to their immediate friends. Usually, long chains of method calls across multiple objects are not a good idea, yet the main benefit of this RFC is to reduce the boilerplate needed for that scenario.
I agree that long method chains are probably a bad idea, but one or two is fine in a lot of cases. But I very vehemently disagree that method chains are always a bad idea (i.e., the Law of Demeter) -- or basically any hard and fast rule about programming, for that matter. It can be done tastefully, and for tasteful uses this reduces a lot of boilerplate. Basically every language feature can be abused, and I don't think this one is particularly more prone to abuse than anything else.
Josh
Hi again.
First, how is this substantially different from catching an exception? With Nikita’s Exceptions in the Engine RFC, something like this would be possible:
try {
return $foo->bar()->qux()->elePHPant()->dance();
} catch (EngineException $e) {
return NULL;
}That would essentially do the same as what you’re proposing:
return $foo?->bar()?->qux()?->elePHPant()?->dance();
Are there many instances where there’d be a substantial benefit to using ?-> versus catching an exception?
It's an API decision up to the implementors of the object. If you like using exceptions for every failure, then go for it, this syntax won't cost you anything or get in your way :)
However, for a lot of failures, I don't feel that exceptions are appropriate. I tend to only use them for exceptional behavior -- usually, some failure that needs to be propagated up a few levels up the stack, to an appropriate error boundary. This doesn't necessarily mean a completely unrecoverable error, but it's locally unrecoverable. For things that are completely recoverable, returning null might be more appropriate and more lightweight, and then this nullsafe operator would be useful at that point.
…huh? I’m not talking about exceptions the object itself throws. In PHP 7, if Nikita’s RFC passes, trying to call a method on NULL
will throw an exception. That largely negates the need for a special operator, since you can just catch the exception.
Thanks.
Andrea Faulds
http://ajf.me/
-----Ursprüngliche Nachricht-----
Von: Andrea Faulds [mailto:ajf@ajf.me]
Gesendet: Mittwoch, 10. Dezember 2014 20:21
An: Josh Watzman
Cc: PHP internals
Betreff: Re: [PHP-DEV] [RFC] Nullsafe callsHi again.
First, how is this substantially different from catching an exception? With Nikitas Exceptions in the Engine RFC,
something like this would be possible:try {
return $foo->bar()->qux()->elePHPant()->dance();
} catch (EngineException $e) {
return NULL;
}That would essentially do the same as what youre proposing:
return $foo?->bar()?->qux()?->elePHPant()?->dance();
Are there many instances where thered be a substantial benefit to using ?-> versus catching an exception?
It's an API decision up to the implementors of the object. If you like
using exceptions for every failure, then go for it, this syntax
won't cost you anything or get in your way :)However, for a lot of failures, I don't feel that exceptions are appropriate. I tend to only use them for
exceptional
behavior -- usually, some failure that needs to be propagated up a few levels up the stack, to an appropriate error
boundary. This doesn't necessarily mean a completely unrecoverable error, but it's locally unrecoverable. For things
that
are completely recoverable, returning null might be more appropriate and more lightweight, and then this nullsafe
operator would be useful at that point.huh? Im not talking about exceptions the object itself throws. In PHP 7, if Nikitas RFC passes, trying to call a
method on
NULL
will throw an exception. That largely negates the need for a special operator, since you can just catch the
exception.Thanks.
Andrea Faulds
http://ajf.me/--
I think it is a matter of taste whether you use try/catch as control structure or only as exception handling. I for
myself usually try to avoid using it as control structure and thus would write something like:
if($a !== null){
$a->foo(bar());
}
And for this case the ?-> operator seems perfect for me - as long as it does not evaluate bar().
.huh? I'm not talking about exceptions the object itself throws. In PHP 7, if Nikita's RFC passes, trying to call a method on
NULL
will throw an exception. That largely negates the need for a special operator, since you can just catch the exception.Thanks.
Andrea Faulds
I think it is a matter of taste whether you use try/catch as control structure or only as exception handling. I for
myself usually try to avoid using it as control structure and thus would write something like:
if($a !== null){
$a->foo(bar());
}And for this case the ?-> operator seems perfect for me
Yeah, this is exactly the point I was trying to make, sorry it wasn't clear: that when to use exceptions are up to taste, and in my taste (and others'), catching exceptions here aren't appropriate in the slightest. (Not that throwing them is wrong -- if you expect that object to be non-null, then throwing one is great -- my problem is with dealing with that failure by immediately catching it. See my original post for the extended explanation.)
So I find this operator really useful here.
Josh
Josh Watzman wrote:
However, for a lot of failures, I don't feel that exceptions are
appropriate. I tend to only use them for exceptional behavior --
usually, some failure that needs to be propagated up a few levels up
the stack, to an appropriate error boundary. This doesn't necessarily
mean a completely unrecoverable error, but it's locally
unrecoverable. For things that are completely recoverable, returning
null might be more appropriate and more lightweight, and then this
nullsafe operator would be useful at that point.
FWIW, there is a third option: instead of returning null, return a null
object[1]. That causes (slightly) more overhead, but it's quite
flexible and works rather nicely without requiring the (IMO) ugly ?->
nor exception handling.
Consider a non-existent user "foo":
User::find('foo')?->getName() // returns null
User::find('foo')->getName() // could also return '' or 'Unknown user'
[1] http://en.wikipedia.org/wiki/Null_Object_pattern
--
Christoph M. Becker
Josh Watzman wrote:
However, for a lot of failures, I don't feel that exceptions are
appropriate. I tend to only use them for exceptional behavior --
usually, some failure that needs to be propagated up a few levels up
the stack, to an appropriate error boundary. This doesn't necessarily
mean a completely unrecoverable error, but it's locally
unrecoverable. For things that are completely recoverable, returning
null might be more appropriate and more lightweight, and then this
nullsafe operator would be useful at that point.FWIW, there is a third option: instead of returning null, return a null
object[1]. That causes (slightly) more overhead, but it's quite
flexible and works rather nicely without requiring the (IMO) ugly ?->
nor exception handling.Consider a non-existent user "foo":
User::find('foo')?->getName() // returns null
User::find('foo')->getName() // could also return '' or 'Unknown user'
This is actually what the implementation does behind the scenes, but issues with this approach actually being visible to userlevel code is what prompted the feature in the first place :) The biggest problem is that you can accidentally leak your null object, which causes the calling code to unexpectedly swallow errors; with the ?-> operator, you know you're always immediately using the null object, there's no way to hold onto it. Smaller problems are that you either need a different null object for every object you want to use this with, or a single general one with __call, which is ugly and hard to optimize. It's also impossible to typecheck, but that was a problem for Hack and is pretty irrelevant to PHP.
It's also worth noting that doing this with a null object would have the same lack of short circuiting that I've been arguing for ;)
Josh
Hi!
First, how is this substantially different from catching an
exception? With Nikita’s Exceptions in the Engine RFC
(https://wiki.php.net/rfc/engine_exceptions_for_php7), something like
this would be possible:try { return $foo->bar()->qux()->elePHPant()->dance(); } catch
(EngineException $e) { return NULL; }
This actually is not a good code. Exceptions should not be used for
something that is expected and normal, they should rather be used for
situations where you code does something you could not predict.
Programming for exceptions is usually bad style, and designing code flow
expecting exceptions is usually bad idea. So I'd actually prefer being
able, like, say, in Groovy, to say "try to call this long chain of
whatever, but if it doesn't work out, just give me null". Of course, it
is not for carefully controlled code with error reporting, but for some
prototype code dealing with dirty data I found such shortcuts be rather
helpful. I'd like PHP to be more Groovy and less Java, in other words ;)
Second, not short-circuiting is rather unintuitive. I think most
people would expect it to short-circuit. Note that ?? and isset()
short-circuit, so this would be inconsistent with existing
NULL-checking operators. So why, then, are you proposing that it
should not short-circuit? Is there some obscure case that this makes
easier? If anything, I’d expect that short-circuiting is the more
useful behaviour. For example, take the following hypothetical line
of code:$elePHPantParty->danceClub?->addToDanceFloor($elePHPantPool->remove());
I agree, for this feature I think it is a must that it would have no
magic like evaluating code way past the first null in ?-> chain. As soon
as there's null on the left of ?->, we should be done and return null,
it should work just like the if would work. In fact, it can even compile
to the same code as if() would.
Stas Malyshev
smalyshev@gmail.com
Hi,
Not trying to demerit the proposal, but accepting ?-> and black magic just
seems wrong, very wrong.
You need to write defensive code, not rely on the language to do that for
you.
[]s,
On Thu, Dec 11, 2014 at 2:35 AM, Stanislav Malyshev smalyshev@gmail.com
wrote:
Hi!
First, how is this substantially different from catching an
exception? With Nikita’s Exceptions in the Engine RFC
(https://wiki.php.net/rfc/engine_exceptions_for_php7), something like
this would be possible:try { return $foo->bar()->qux()->elePHPant()->dance(); } catch
(EngineException $e) { return NULL; }This actually is not a good code. Exceptions should not be used for
something that is expected and normal, they should rather be used for
situations where you code does something you could not predict.
Programming for exceptions is usually bad style, and designing code flow
expecting exceptions is usually bad idea. So I'd actually prefer being
able, like, say, in Groovy, to say "try to call this long chain of
whatever, but if it doesn't work out, just give me null". Of course, it
is not for carefully controlled code with error reporting, but for some
prototype code dealing with dirty data I found such shortcuts be rather
helpful. I'd like PHP to be more Groovy and less Java, in other words ;)Second, not short-circuiting is rather unintuitive. I think most
people would expect it to short-circuit. Note that ?? and isset()
short-circuit, so this would be inconsistent with existing
NULL-checking operators. So why, then, are you proposing that it
should not short-circuit? Is there some obscure case that this makes
easier? If anything, I’d expect that short-circuiting is the more
useful behaviour. For example, take the following hypothetical line
of code:$elePHPantParty->danceClub?->addToDanceFloor($elePHPantPool->remove());
I agree, for this feature I think it is a must that it would have no
magic like evaluating code way past the first null in ?-> chain. As soon
as there's null on the left of ?->, we should be done and return null,
it should work just like the if would work. In fact, it can even compile
to the same code as if() would.Stas Malyshev
smalyshev@gmail.com--
--
Guilherme Blanco
MSN: guilhermeblanco@hotmail.com
GTalk: guilhermeblanco
Toronto - ON/Canada
guilhermeblanco@gmail.com wrote on 11/12/2014 15:57:
Not trying to demerit the proposal, but accepting ?-> and black magic just
seems wrong, very wrong.
You need to write defensive code, not rely on the language to do that for
you.
But surely if you're consciously deciding to use this feature, it is
defensive code - just defensive code involving less copy-and-paste of if
( ! is_null($blah) )...
I agree, doing lots of if statements gets bloated. Someone would only
use this method if they was comfortable with it either returning null or
the desired result.
guilhermeblanco@gmail.com wrote on 11/12/2014 15:57:
Not trying to demerit the proposal, but accepting ?-> and black magic
just
seems wrong, very wrong.
You need to write defensive code, not rely on the language to do that
for
you.But surely if you're consciously deciding to use this feature, it is
defensive code - just defensive code involving less copy-and-paste of
if ( ! is_null($blah) )...