On Thu, 24 Oct 2019 at 23:49, Ken Stanley <doh...@gmail.com> wrote: > > On Thu, Oct 24, 2019 at 4:29 PM Andreas Hennings <andr...@dqxtech.net> wrote: >> >> On Thu, 24 Oct 2019 at 20:59, Ken Stanley <doh...@gmail.com> wrote: >> > >> > On Thu, Oct 24, 2019 at 1:33 PM Dan Ackroyd <dan...@basereality.com> wrote: >> > >> > > On Thu, 24 Oct 2019 at 18:21, Ken Stanley <doh...@gmail.com> wrote: >> > > > >> > > > Since PHP 7.0 brought forward the Null Coalescing Operator (??), >> > > > writing >> > > > more succinct code for how to handle null values has been a blessing. >> > > But, >> > > > what about the inverse when you want to do something when a value is >> > > > not >> > > > null? >> > > >> > > Hi Ken, >> > > >> > > It may help to give a real world example, rather than a metasyntactic >> > > one, as I can't immediately see how this would be useful. >> > > >> > > People have been expressing a concern over 'symbol soup' for similar >> > > ideas. The null colalesce scenario happens frequently enough, that it >> > > seemed to overcome the hurdle needed for acceptance. Again, giving a >> > > real world example of what you currently need to do frequently might >> > > help other people understand the need. >> > > >> > > cheers >> > > Dan >> > > >> > >> > Hi Dan, >> > >> > After some thought, and searching through my existing code bases, I believe >> > I've come up with a decent code example to help demonstrate the usefulness >> > of the proposed anti-coalescing-operator: >> > >> > Without !??: >> > <?php >> > >> > class ExampleController >> > { >> > /** >> > * PATCH a User object. >> > */ >> > public function saveAction(int $userId) >> > { >> > $user = $this->getUser($userId); >> > >> > if (isset($_SERVER['fname']) { >> > $user->setName($_SERVER['fname']); >> > } >> > >> > if (isset($_SERVER['lname']) { >> > $user->setName($_SERVER['lname']); >> > } >> > >> > if (isset($_SERVER['mname']) { >> > $user->setName($_SERVER['mname']); >> > } >> > >> > if (isset($_SERVER['phone']) { >> > $user->setName($_SERVER['phone']); >> > } >> > >> > if (isset($_SERVER['email']) { >> > $user->setName($_SERVER['email']); >> > } >> > >> > $this-saveUser($user); >> > } >> > } >> > >> > With !??: >> > <?php >> > >> > class ExampleController >> > { >> > /** >> > * PATCH a User object. >> > */ >> > public function saveAction(int $userId) >> > { >> > $user = $this->getUser($userId); >> > >> > $_SERVER['fname'] !?? $user->setName($_SERVER['fname']); >> > $_SERVER['lname'] !?? $user->setName($_SERVER['lname']); >> > $_SERVER['mname'] !?? $user->setName($_SERVER['mname']); >> > $_SERVER['phone'] !?? $user->setName($_SERVER['phone']); >> > $_SERVER['email'] !?? $user->setName($_SERVER['email']); >> > >> > $this-saveUser($user); >> > } >> > } >> > Thank you, >> > Ken Stanley >> >> Not convinced. >> 1. Most of the perceived brevity is from omitting line breaks and >> curly brackets, which is a bit misleading imo. > > > This argument can be made about ?? And ?:, which have already passed muster > and creates a precedent.
But these were designed to produce a value, not for just control flow - see my other point. Yes, the same argument could have been made for those, if a similar example had been given to advertise their introduction. > Additionally, this is meant to compliment the existing ?? by adding a > negation counterpart (similar to how == has !== and > has <). > > I’m curious to what you find misleading about it? Its meant to literally be > the not-null coalescing operator. Misleading as in "look how much shorter this is". > >> >> 2. It is not the intended use of these kinds of operators (ternary or >> null coalesce). Normally you would use them to produce a value, here >> you use them for control flow only. > > > Here is another example, not using them for flow control: > > <?php > > class Foo > { > /** > * @return User|null > */ > private $user; > > /** > * @return string|null > */ > public function getName(): ?string > { > return $this->user !?? $this->user->getName(); > } > } > > compared to: > <?php > > class Foo > { > /** > * @return User|null > */ > private $user; > > /** > * @return string|null > */ > public function getName(): ?string > { > // or $this->user instanceof User > return $this->user !== null ? $this->user->getName() : null; > } > } This example is better indeed. But here I would prefer to have the ?-> operator proposed earlier (nullsafe calls). return $this->user?->getName(); >> >> 3. One purpose of the operator should be that you don't have to repeat >> the variable. Here you do, e.g. $_SERVER['fname'] >> > > I'm not sure how that's necessarily a bad thing. Would you elaborate? Is it > simply a matter of writing the same characters twice? How is that different > than: > > if (isset($_SERVER['fname'])) { > $user->setName($_SERVER['fname']); > } I thought you like brevity? "terse and simple"? Ofc it is not just about typing more characters, but about having two places that need to be updated if we change the value expression. All the arguments for DRY apply. And yes, with existing if/else code we would also have this kind of repetition for this use case. But if we introduce a new operator, we would expect this to go away, wouldn't we? The repetition becomes more relevant if the expression we would repeat is really long: isset($something['something']['something']['something']) !?? $something['something']['something']['something']->foo(); > >> >> 1. >> If you would simply omit the line breaks in the first version, you >> would get this: >> >> if (isset($_SERVER['fname'])) $user->setName($_SERVER['fname']); >> if (isset($_SERVER['lname'])) $user->setName($_SERVER['lname']); >> if (isset($_SERVER['mname'])) $user->setName($_SERVER['mname']); >> if (isset($_SERVER['phone'])) $user->setName($_SERVER['phone']); >> if (isset($_SERVER['email'])) $user->setName($_SERVER['email']); > > > Ugh! I just noticed that I mistakenly did not update the method names for > each line, so I can see how this might look like flow control. Those methods > should be setFirstName, setLastName, setMiddleName, setPhone, and setEmail > respectively. My apologies. I meant "only control flow" in the sense that we are not returning a value, or rather, we do not read the value. Ofc it makes a more lively example to have different methods, but I was looking past that already :) > >> >> >> 2. >> Instead of "abusing" your new operator, you could simply "abuse" the >> old ternary ?: instead: >> >> !isset($_SERVER['fname']) ?: $user->setName($_SERVER['fname']); >> !isset($_SERVER['lname']) ?: $user->setName($_SERVER['lname']); >> !isset($_SERVER['mname']) ?: $user->setName($_SERVER['mname']); >> !isset($_SERVER['phone']) ?: $user->setName($_SERVER['phone']); >> !isset($_SERVER['email']) ?: $user->setName($_SERVER['email']); >> > > Re; #1 and #2 here: the same argument can be made for both ?? and ?:, which > again, has already passed muster and set precedent. > As before, they were not really designed for this case. But if we do use them, they already go a long way. >> >> 3. >> One way to not repeat the variable would be to introduce a temporary >> local variable, like so: >> >> if (NULL !== $fname = $_SERVER['fname'] ?? NULL) $user->setName($fname); > > The entire point of !?? would be to keep things terse and simple in nature. > Does it fit every possible use case? No. But neither do ?? and ?:. And, like > ?? and ?:, no one is required to use it if they feel being more verbose makes > sense for their needs. > >> >> >> This gets more useful if the variable expression is something longer. >> >> A new language feature for this purpose could have an anatomy like this: >> https://3v4l.org/TjuuO or >> https://3v4l.org/U6arm >> >> and the short syntax would be like so: >> >> $product = ($x ??! NULL) * ($y ??! NULL); >> >> or the NULL can be omitted: >> >> $product = ($x ??!) * ($y ??!); > > > Not sure if the ??! was on purpose as an alternative to !??, but given how > other operators put the negation operator first, I feel we should maintain > the common standard. I don't really think of it as a "negated ??". The motivation to have a negation seems quite theoretical to me. I am more interested in actual use cases. > >> >> >> So, the operator would break out of the current expression context, >> and produce a value one level up, a bit like a try/throw/catch would, >> or like a break in switch. >> > > Isn't this the same as the aforementioned null-safe operator > [https://wiki.php.net/rfc/nullsafe_calls]? Except that ?-> only works for method chaining, not for function args. (as pointed out in a previous message) -- Andreas > >> >> This is just a basic idea, it still leaves a lot of questions open. >> If the expression context is multiple levels deep, how many of these >> levels are we breaking? >> >> I am not suggesting this is a good idea, but I think it is an >> improvement to the original proposal. >> >> -- Andreas > > > > Thank you for the great feedback! -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php