On Dec 10, 2014, at 9:19 AM, Andrea Faulds <[email protected]> wrote:
> 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
--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php