Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:79539 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 64187 invoked from network); 10 Dec 2014 19:11:45 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 10 Dec 2014 19:11:45 -0000 Authentication-Results: pb1.pair.com header.from=jwatzman@fb.com; sender-id=unknown Authentication-Results: pb1.pair.com smtp.mail=jwatzman@fb.com; spf=unknown; sender-id=unknown Received-SPF: unknown (pb1.pair.com: domain fb.com does not designate 67.231.145.42 as permitted sender) X-PHP-List-Original-Sender: jwatzman@fb.com X-Host-Fingerprint: 67.231.145.42 mx0a-00082601.pphosted.com Linux 2.5 (sometimes 2.4) (4) Received: from [67.231.145.42] ([67.231.145.42:55975] helo=mx0a-00082601.pphosted.com) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id 06/F6-29826-0FA98845 for ; Wed, 10 Dec 2014 14:11:45 -0500 Received: from pps.filterd (m0004348 [127.0.0.1]) by m0004348.ppops.net (8.14.5/8.14.5) with SMTP id sBAJ8gCE019660 for ; Wed, 10 Dec 2014 11:11:41 -0800 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=fb.com; h=from : to : cc : subject : date : message-id : references : in-reply-to : content-type : content-id : content-transfer-encoding : mime-version; s=facebook; bh=CFAFlTVhBtWFpXCevuPz14OcWm7GwGdLf/GiYIawH/4=; b=a0IGHoFAbbwfMJ8U70s55AGkSKpWCBiPKxl4vBjNYQH+P3RlC6/uJn6Zr/yaHB5oPZMk hjGgbsD+z8+j9lhgdWcW0BpfSIIK9oEOb7vg13TCXzqpZqhBNL9Ke4i2DfKT1CwNbS2m dF53vNqY9qmpAjrV4zGBycsZTPlWcIbR5yw= Received: from pps.reinject (localhost [127.0.0.1]) by m0004348.ppops.net with ESMTP id 1r6grf19kb-1 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Wed, 10 Dec 2014 11:11:40 -0800 Received: from m0004348 (m0004348 [127.0.0.1]) by pps.reinject (8.14.5/8.14.5) with SMTP id sBAJBeRo022635 for ; Wed, 10 Dec 2014 11:11:40 -0800 Received: from mail.thefacebook.com ([199.201.64.23]) by m0004348.ppops.net with ESMTP id 1r6grf19k9-1 (version=TLSv1/SSLv3 cipher=AES128-SHA bits=128 verify=OK); Wed, 10 Dec 2014 11:11:40 -0800 Received: from PRN-MBX02-2.TheFacebook.com ([169.254.5.125]) by PRN-CHUB15.TheFacebook.com ([fe80::3479:9544:c2df:1fc0%12]) with mapi id 14.03.0195.001; Wed, 10 Dec 2014 11:11:39 -0800 To: Andrea Faulds CC: PHP internals Thread-Topic: [PHP-DEV] [RFC] Nullsafe calls Thread-Index: AQHQFATxvp9RjOsogUO8aMy5/EMi+5yJmRsAgAAfIAA= Date: Wed, 10 Dec 2014 19:11:38 +0000 Message-ID: <583B9A4B-68FD-4FB1-B043-0D86E2480EAD@fb.com> References: <95A581EE-A062-4926-BE44-BCA87FC9B356@fb.com> <9230CB24-22FC-4A0E-A9D5-F02523B65A02@ajf.me> In-Reply-To: <9230CB24-22FC-4A0E-A9D5-F02523B65A02@ajf.me> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-originating-ip: [192.168.16.4] Content-Type: text/plain; charset="Windows-1252" Content-ID: Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:5.13.68,1.0.33,0.0.0000 definitions=2014-12-10_08:2014-12-10,2014-12-10,1970-01-01 signatures=0 X-Proofpoint-Spam-Details: rule=fb_default_notspam policy=fb_default score=0 kscore.is_bulkscore=1.11022302462516e-16 kscore.compositescore=0 circleOfTrustscore=71.128 compositescore=0.994849863700292 urlsuspect_oldscore=0.994849863700292 suspectscore=0 recipient_domain_to_sender_totalscore=0 phishscore=0 bulkscore=0 kscore.is_spamscore=0 recipient_to_sender_totalscore=0 recipient_domain_to_sender_domain_totalscore=8891 rbsscore=0.994849863700292 spamscore=0 recipient_to_sender_domain_totalscore=0 urlsuspectscore=0.9 adultscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=7.0.1-1402240000 definitions=main-1412100181 X-FB-Internal: deliver Subject: Re: [PHP-DEV] [RFC] Nullsafe calls From: jwatzman@fb.com (Josh Watzman) On Dec 10, 2014, at 9:19 AM, Andrea Faulds wrote: > Hi again, >=20 > I was in favour of this, but upon further thought, I=92m not sure I am. I= remember I also initially liked it in Hack and then lost interest. >=20 > First, how is this substantially different from catching an exception? Wi= th Nikita=92s Exceptions in the Engine RFC, something like this would be po= ssible: >=20 > try { > return $foo->bar()->qux()->elePHPant()->dance(); > } catch (EngineException $e) { > return NULL; > } >=20 > That would essentially do the same as what you=92re proposing: >=20 > return $foo?->bar()?->qux()?->elePHPant()?->dance(); >=20 > Are there many instances where there=92d be a substantial benefit to usin= g ?-> versus catching an exception? It's an API decision up to the implementors of the object. If you like usin= g exceptions for *every* failure, then go for it, this syntax won't cost yo= u anything or get in your way :) However, for a lot of failures, I don't feel that exceptions are appropriat= e. I tend to only use them for exceptional behavior -- usually, some failur= e that needs to be propagated up a few levels up the stack, to an appropria= te error boundary. This doesn't necessarily mean a completely unrecoverable= error, but it's *locally* unrecoverable. For things that are completely re= coverable, returning null might be more appropriate and more lightweight, a= nd 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 wan= t 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 y= ou 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, an= d so you should throw an exception. An error boundary in the news feed infr= a code is what catches the exception, several levels up, and just removes t= hat single story from feed, and moves on. It's locally unrecoverable, thoug= h globally we can still load feed. In the other direction, if you've got mo= st 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 reco= verable, some User you can't see just gets left off the list of likers, gre= at. We don't want to abort any operation at all, we can keep going; excepti= ons 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 wa= nt to work. But not everyone does -- I at least think it makes sense to wor= k the way I've described, and there are a lot of points on that spectrum, a= nd this new operator makes it easy to operate at different points there. > Second, not short-circuiting is rather unintuitive. I think most people w= ould 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 obs= cure case that this makes easier? If anything, I=92d expect that short-circ= uiting is the more useful behaviour. For example, take the following hypoth= etical line of code: >=20 > $elePHPantParty->danceClub?->addToDanceFloor($elePHPantPool->remove())= ; >=20 > If we have the short-circuiting behaviour, then if $elePHPantParty->dance= Club is NULL, ->addToDanceFloor isn=92t called and an ElePHPant isn=92t rem= oved from $elePHPantPool. On the other hand, if ->danceClub isn=92t NULL, -= >addToDanceFloor is called and an ElePHPant will be removed from $elePHPant= Pool. >=20 > With the non-short-circuiting behaviour, this code would have to be longe= r, and you couldn=92t use ?-> here. Instead, you=92d have to write this: >=20 > if ($elePHPantParty->danceClub !=3D=3D NULL) { > $elePHPantParty->danceClub->addToDanceFloor($elePHPantPool->remove= ()); > } >=20 > It is, admittedly, a hypothetical scenario, but it does demonstrate why h= aving it not short-circuit is less useful. Are there any examples where sho= rt-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 happ= en at the last moment. That said, although I feel pretty strongly about thi= s, I also didn't find a good example where it mattered either way when I we= nt looking at existing usages in Hack, and your example is indeed an intere= sting one. Let me spend some more time looking for examples and get back to= you. > Third, as has already been mentioned, this doesn=92t support ?-> for prop= erties, which is both unintuitive (people will reasonably expect it to work= ) and makes it a lot less useful, because you won=92t always be dealing wit= h method calls. While $foo?->bar()?->foobar() might work, what about someth= ing like $foo?->bar?->foobar(), where one of the links in the chain is a pr= operty? 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 easie= r 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. t= hat objects should only talk to their immediate friends. Usually, long chai= ns of method calls across multiple objects are not a good idea, yet the mai= n 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 a= re *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