On Dec 10, 2014, at 9:19 AM, Andrea Faulds <a...@ajf.me> 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

Reply via email to