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? You're left with the traditional (and sometimes verbose) methods of
using if conditions or the full-blown ternary operator $foo !== null ? <something> : null;
.
I am here to gauge the interest of the community in having an
anti-coalescing operator that would execute code only if the condition is
not null, otherwise returning null in its stead. If this is not a wholly
terrible idea, I will -- as suggested -- then continue to create an
official RFC.
My initial thought process would be to use a similar syntax to the Null
Coalescing Operator (??), but to add negation: $bar = $foo !?? $foo->getBar()
. I didn't want to use !?
because then if anybody wanted
to add an anti-ternary operator (e.g., !?:
) for falsey comparison, then
they'd have to get creative or use the three-character approach I am
already proposing. The purpose is simply to negate the existing behavior,
so at first glance I do not see any BC breakages or additional complexities
that haven't already been addressed - naturally, I could be wrong.
The driving use case, as hinted at in my above example, is if I have a
nullable object I only want to call a method on it if the object is not
null. Otherwise, default to the null value.
I am working through the RFC Howto [https://wiki.php.net/rfc/howto], and it
suggests that I ask for wiki karma for my account (897syudjhf) so that I
may create the RFC when appropriate.
Additionally, I have not [yet] found any existing precedence for
anti-coalescing; i.e., there does not seem to be an elegant counterpart to
COALESCE() in SQL. And, as they say, naming is hard. If
anti-coalescing-operator
does not sit well with you all, I am open to
naming suggestions.
Thank you for your time,
Ken Stanley
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
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 Ken,
This sounds like an alternative approach (for solving the same basic problem) to the nullsafe operator discussed a while back, no?
https://wiki.php.net/rfc/nullsafe_calls
Cheers
Stephen
On Thu, Oct 24, 2019 at 1:46 PM Stephen Reay php-lists@koalephant.com
wrote:
Hi Ken,
This sounds like an alternative approach (for solving the same basic
problem) to the nullsafe operator discussed a while back, no?
https://wiki.php.net/rfc/nullsafe_callsCheers
Stephen
Hi Stephen!
Yes, it would be similar if the object you're testing against is the same
object you want to call (e.g., $foo !?? $foo->getBar()). But, if the
variable is not an object, or it's just a test for another object, then the
nullsafe operator does not seem to address this situation (e.g., $userId
!?? $bar->getUser($userId())). In fact, I could see this RFC complimenting
the nullsafe operator: $userId !?? $bar->getUser($userId)?->getName().
Thank you,
Ken Stanley
PS. Dan, I am going to respond to your request for a code example; I am
taking my time so that I can give you and everyone else a good example. :)
On Thu, Oct 24, 2019 at 12:46 PM Stephen Reay php-lists@koalephant.com
wrote:
This sounds like an alternative approach (for solving the same basic
problem) to the nullsafe operator discussed a while back, no?
https://wiki.php.net/rfc/nullsafe_calls
At the risk of hijacking, @matthewrask asked me about ?-> a couple weeks
ago (Javascript is making this one popular), and I threw together a rough
PoC at
https://github.com/php/php-src/compare/master...sgolemon:null-coalesce which
I suspect he intends to RFC properly soon. As long as the topic is at
hand, what's the general appetite for it? Should I bother polishing the
turd?
-Sara
On Thu, Oct 24, 2019 at 12:46 PM Stephen Reay php-lists@koalephant.com
wrote:This sounds like an alternative approach (for solving the same basic
problem) to the nullsafe operator discussed a while back, no?https://wiki.php.net/rfc/nullsafe_calls
At the risk of hijacking, @matthewrask asked me about ?-> a couple weeks
ago (Javascript is making this one popular), and I threw together a rough
PoC at
https://github.com/php/php-src/compare/master...sgolemon:null-coalesce which
I suspect he intends to RFC properly soon. As long as the topic is at
hand, what's the general appetite for it? Should I bother polishing the
turd?-Sara
?-> is a great idea.
The problem is this only works for method chaining, not for function
argument nesting.
So we might want something else in addition to that.
foo(bar(baz() ???) ???);
-- Andreas
?-> is a great idea.
Totally agree, that would be nice.
The problem is this only works for method chaining, not for function
argument nesting.
So we might want something else in addition to that.foo(bar(baz() ???) ???);
I am confused about this. Why would this be needed? In case the functions baz(), bar() or foo() do not exist?
Please help me see what I am missing.
-Mike
At the risk of hijacking, @matthewrask asked me about ?-> a couple weeks
ago (Javascript is making this one popular), and I threw together a rough
PoC at
https://github.com/php/php-src/compare/master...sgolemon:null-coalesce
which
I suspect he intends to RFC properly soon. As long as the topic is at
hand, what's the general appetite for it? Should I bother polishing the
turd?
It would be the best Christmas present ever.
Well. Almost.
Peter
On Wed, Oct 30, 2019 at 7:35 AM Peter Bowyer phpmailinglists@gmail.com
wrote:
At the risk of hijacking, @matthewrask asked me about ?-> a couple weeks
ago (Javascript is making this one popular), and I threw together a rough
PoC at
https://github.com/php/php-src/compare/master...sgolemon:null-coalesce
which
I suspect he intends to RFC properly soon. As long as the topic is at
hand, what's the general appetite for it? Should I bother polishing the
turd?It would be the best Christmas present ever.
Well. Almost.
Peter
Folks, I haven't forgotten about this; in fact, I've been thinking about it
all weekend, and I cannot come up with a good use-case that hasn't already
been shown to have either a viable current work-around, or an RFC (with
code already written) that would give me what I was looking to do with my
!?? operator syntax.
So, with that said, I see no reason to further the discussion on a not-null
coalescing operator. I do want to thank everybody for their kind and
thoughtful feedback. You really made me think about this, and I enjoyed it.
I will continue to look for something I can cut my teeth on as a first-time
contribution to php core.
- Ken Stanley
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
Hi Ken,
This is definitely an interesting idea, and when described as "the
opposite of ??" the !?? syntax makes sense.
Looking at the example, though, the "negating" part becomes a bit confusing.
The verbose form reads naturally as a positive assertion - "if this is
set, do this":
if (isset($_SERVER['fname']) { $user->setName($_SERVER['fname']); }
If we used the ?? operator, it would be a negative, "if this is not set,
do this":
$_SERVER['fname'] ?? $user->unsetName();
So the short-hand form becomes a kind of double negative - "if this is
not set, don't do this", or "if this is not not set, do this":
$_SERVER['fname'] !?? $user->setName($_SERVER['fname']);
That makes me think that the choice of syntax isn't quite right, but I'm
not sure what to suggest instead.
Regards,
--
Rowan Tommins (né Collins)
[IMSoP]
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
DanHi 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 !??:
<?phpclass 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 !??:
<?phpclass 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.
-
Most of the perceived brevity is from omitting line breaks and
curly brackets, which is a bit misleading imo. -
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. -
One purpose of the operator should be that you don't have to repeat
the variable. Here you do, e.g. $_SERVER['fname']
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']);
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']);
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);
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 ??!);
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.
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
Hi Ken
I totally agree with Andreas especially:
One purpose of the operator should be that you don't have to repeat
the variable. Here you do, e.g. $_SERVER['fname']
But if this operator provide some way not to repeat the variable it will
make sense. For example:
$_SERVER['fname'] !?? $user->setName($$)
But I think this functionality should be of something like pipeline
operator:
// send tmp variable to the next expression unconditionally
$ret = $_SERVER['fname'] |> $user->setName($$);
// send tmp variable to the next expression only when $_SERVER['fname'] is
set.
$ret = $_SERVER['fname'] ?|> $user->setName($$);
Also the syntax like above will be consistent with the proposed
safe-navigation operators.
Cheers
On Fri, Oct 25, 2019 at 3:29 AM Andreas Hennings andreas@dqxtech.net
wrote:
On Thu, Oct 24, 2019 at 1:33 PM Dan Ackroyd Danack@basereality.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
DanHi 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 !??:
<?phpclass 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 !??:
<?phpclass 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 StanleyNot convinced.
Most of the perceived brevity is from omitting line breaks and
curly brackets, which is a bit misleading imo.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.One purpose of the operator should be that you don't have to repeat
the variable. Here you do, e.g. $_SERVER['fname']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']);
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']);
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);
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/U6armand the short syntax would be like so:
$product = ($x ??! NULL) * ($y ??! NULL);
or the
NULL
can be omitted:$product = ($x ??!) * ($y ??!);
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.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
On Thu, Oct 24, 2019 at 5:19 PM Kosit Supanyo webdevxp.com@gmail.com
wrote:
Hi Ken
I totally agree with Andreas especially:
One purpose of the operator should be that you don't have to repeat
the variable. Here you do, e.g. $_SERVER['fname']
But if this operator provide some way not to repeat the variable it will
make sense. For example:$_SERVER['fname'] !?? $user->setName($$)
I asked Andreas for their opinion on why this is bad in this context?
Especially since if I were to use a traditional ternary or if condition,
then I'd still be repeating the variable.
But I think this functionality should be of something like pipeline
operator:// send tmp variable to the next expression unconditionally
$ret = $_SERVER['fname'] |> $user->setName($$);
// send tmp variable to the next expression only when $_SERVER['fname'] is
set.
$ret = $_SERVER['fname'] ?|> $user->setName($$);Also the syntax like above will be consistent with the proposed
safe-navigation operators.
I'm more interested in having a negation operator for the null-coalescing
operator, especially since cognatively it should be easy to discern what it
does. I've personally not seen |>, and I'd be confused as to what it does
(e.g., is it a bitwise OR on a greater than comparison?).
Thank you! The idea of $$ is neat, but not quite what I'm trying to achieve
here. If anything, wouldn't that facilitate a different RFC?
- Ken Stanley
I'm more interested in having a negation operator for the null-coalescing
operator, especially since cognatively it should be easy to discern what it
does.
At the point your syntax ends up looking like you're screaming at your
source code, I think easy cognition has likely gone out the window.
There are plenty of much more expressive ways of doing this without
introducing new syntax IMHO.
Mark Randall
Hi Ken
|>
is not something new it appeared in many languages long time ago (in
some forms). JavaScript also has a proposal of it. And PHP someone has
already proposed one.
https://github.com/tc39/proposal-pipeline-operator
https://wiki.php.net/rfc/pipe-operator
Cheers
On Thu, Oct 24, 2019 at 5:19 PM Kosit Supanyo webdevxp.com@gmail.com
wrote:Hi Ken
I totally agree with Andreas especially:
One purpose of the operator should be that you don't have to repeat
the variable. Here you do, e.g. $_SERVER['fname']
But if this operator provide some way not to repeat the variable it will
make sense. For example:$_SERVER['fname'] !?? $user->setName($$)
I asked Andreas for their opinion on why this is bad in this context?
Especially since if I were to use a traditional ternary or if condition,
then I'd still be repeating the variable.But I think this functionality should be of something like pipeline
operator:// send tmp variable to the next expression unconditionally
$ret = $_SERVER['fname'] |> $user->setName($$);
// send tmp variable to the next expression only when $_SERVER['fname']
is set.
$ret = $_SERVER['fname'] ?|> $user->setName($$);Also the syntax like above will be consistent with the proposed
safe-navigation operators.I'm more interested in having a negation operator for the null-coalescing
operator, especially since cognatively it should be easy to discern what it
does. I've personally not seen |>, and I'd be confused as to what it does
(e.g., is it a bitwise OR on a greater than comparison?).Thank you! The idea of $$ is neat, but not quite what I'm trying to
achieve here. If anything, wouldn't that facilitate a different RFC?
- Ken Stanley
But I think this functionality should be of something like pipeline
operator:// send tmp variable to the next expression unconditionally
$ret = $_SERVER['fname'] |> $user->setName($$);
// send tmp variable to the next expression only when $_SERVER['fname'] is
set.
$ret = $_SERVER['fname'] ?|> $user->setName($$);Also the syntax like above will be consistent with the proposed
safe-navigation operators.
I really like this idea, and it actually makes the pipeline operator itself
feel more useful, too.
Imagine this chain, where not only might the variable not be set, but it
might not match a user, or the user might have no name:
$upperCaseUserName = $_GET['user_id'] ?|> User::getById($$) ?-> getName()
?|> strtoupper($$);
As well as not needing to repeat the expression each time, as you would
with an "anti-coalesce", "null-safe chain" feels a clearer reading of the
intent here than "if not unset".
Regards,
Rowan Tommins
[IMSoP]
But I think this functionality should be of something like pipeline
operator:// send tmp variable to the next expression unconditionally
$ret = $_SERVER['fname'] |> $user->setName($$);
// send tmp variable to the next expression only when $_SERVER['fname'] is
set.
$ret = $_SERVER['fname'] ?|> $user->setName($$);Also the syntax like above will be consistent with the proposed
safe-navigation operators.I really like this idea, and it actually makes the pipeline operator itself
feel more useful, too.Imagine this chain, where not only might the variable not be set, but it
might not match a user, or the user might have no name:$upperCaseUserName = $_GET['user_id'] ?|> User::getById($$) ?-> getName()
?|> strtoupper($$);As well as not needing to repeat the expression each time, as you would
with an "anti-coalesce", "null-safe chain" feels a clearer reading of the
intent here than "if not unset".
I like this.
It is a bit like a temporary local variable.
One limitation is that you only have one such pipe value at a time.
E.g. it would not cover this:
return isset($a, $b) ? foo($a, $b) : null;
or any nested parameters like foo(bar(baz())) where any intermediate
value could be null.
But I think this functionality should be of something like pipeline
operator:// send tmp variable to the next expression unconditionally
$ret = $_SERVER['fname'] |> $user->setName($$);
// send tmp variable to the next expression only when $_SERVER['fname'] is
set.
$ret = $_SERVER['fname'] ?|> $user->setName($$);Also the syntax like above will be consistent with the proposed
safe-navigation operators.I really like this idea, and it actually makes the pipeline operator itself
feel more useful, too.Imagine this chain, where not only might the variable not be set, but it
might not match a user, or the user might have no name:$upperCaseUserName = $_GET['user_id'] ?|> User::getById($$) ?-> getName()
?|> strtoupper($$);As well as not needing to repeat the expression each time, as you would
with an "anti-coalesce", "null-safe chain" feels a clearer reading of the
intent here than "if not unset".I like this.
It is a bit like a temporary local variable.One limitation is that you only have one such pipe value at a time.
E.g. it would not cover this:return isset($a, $b) ? foo($a, $b) : null;
or any nested parameters like foo(bar(baz())) where any intermediate
value could be null.
Sorry, this last part is wrong of course. The pipe works great for
nested calls. Just with multiple parameters which all can be null it
does not.
On Thu, Oct 24, 2019 at 4:29 PM Andreas Hennings andreas@dqxtech.net
wrote:
On Thu, Oct 24, 2019 at 1:33 PM Dan Ackroyd Danack@basereality.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
DanHi 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 !??:
<?phpclass 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 !??:
<?phpclass 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 StanleyNot convinced.
- 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. 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.
- 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;
}
}
- 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']);
}
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.
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.
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/U6armand 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.
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]?
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!
>
>
>>
>>
>> >
>> >
>> >
>> > >
>> > > >
>> > > > 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!
Hi Ken
$_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']);
What you described is already achievable with short-circuit && :
isset($_SERVER['fname']) && $user->setName($_SERVER['fname']);
Besides, it's a widely used idiom known from shell scripting.
--
Best regards,
Bruce Weirdan mailto:weirdan@gmail.com
Hi Bruce
If I understand correctly.
$_SERVER['fname'] !?? $user->setName($_SERVER['fname']);
Will return the result of $user->setName($_SERVER['fname']) if
$_SERVER['fname'] is set or null if not set. While:
isset($_SERVER['fname']) && $user->setName($_SERVER['fname']);
Will return boolean.
Cheers
Hi Ken
$_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']);
What you described is already achievable with short-circuit && :
isset($_SERVER['fname']) && $user->setName($_SERVER['fname']);
Besides, it's a widely used idiom known from shell scripting.
--
Best regards,
Bruce Weirdan mailto:
weirdan@gmail.com
On Thu, Oct 24, 2019 at 5:31 PM Kosit Supanyo webdevxp.com@gmail.com
wrote:
Hi Bruce
If I understand correctly.
$_SERVER['fname'] !?? $user->setName($_SERVER['fname']);
Will return the result of $user->setName($_SERVER['fname']) if
$_SERVER['fname'] is set or null if not set. While:isset($_SERVER['fname']) && $user->setName($_SERVER['fname']);
Will return boolean.
Yes. Semantically the same, but one is terse and the other is explicit.
Cheers
isset($_SERVER['fname']) && $user->setName($_SERVER['fname']);
Will return boolean.
Just testing the waters: Is there any appetite to have AND and OR behave
more like Pythons operator?
Instead of now:
(a or b) => bool(true) if either of a or b are true
(a and b) => bool(true) is either of a or b are true
Behave as:
(a or b) => Value of a if true, b if a is not true but b is, bool(false)
otherwise.
(a and b) => Value of b if both are true, bool(false) otherwise.
Coincidentally, this change to T_LOGICAL_AND
would server OP's purpose.
I'll tell ya, I'm leaning "No" because BC, but at the same time, AND/OR
seem to be underutilized constructs and I suspect (having done zero
research), that most existing uses would yield the same result as they do
now.
-Sara
isset($_SERVER['fname']) && $user->setName($_SERVER['fname']);
Will return boolean.
Just testing the waters: Is there any appetite to have AND and OR behave
more like Pythons operator?
Similar to javascript || and &&, although not exactly the same.
Instead of now:
(a or b) => bool(true) if either of a or b are true
(a and b) => bool(true) is either of a or b are trueBehave as:
(a or b) => Value of a if true, b if a is not true but b is, bool(false)
otherwise.
(a and b) => Value of b if both are true, bool(false) otherwise.Coincidentally, this change to
T_LOGICAL_AND
would server OP's purpose.I'll tell ya, I'm leaning "No" because BC, but at the same time, AND/OR
seem to be underutilized constructs and I suspect (having done zero
research), that most existing uses would yield the same result as they do
now.
At one point I started using these operators because they have
different operator precedence than || and &&, allowing assignment in
conditions without parentheses.
I gave up on this only to comply with coding standards and to not
confuse people.
I would think we will make a lot of people angry if we change this.
Imo, let's not.
Besides, a lot of this can be achieved with the ?: ternary shortcut
(not sure what's the name).
(a or b) => Value of a if true, b if a is not true but b is, bool(false)
$a ?: ($b ?: false) === ($a ?: $b) ?: false === $a ?: $b ?: false
(a and b) => Value of b if both are true, bool(false) otherwise.
$a ? ($b ?: false) : false === $a ? $b ?: false : false
It is a bit more verbose, I admit. But in most cases we don't need to
replicate the exact behavior, and a simple ?: will do the trick.
-- Andreas
-Sara
Le 25 oct. 2019 à 01:23, Sara Golemon pollita@php.net a écrit :
Just testing the waters: Is there any appetite to have AND and OR behave
more like Pythons operator?
Instead of now:
(a or b) => bool(true) if either of a or b are true
(a and b) => bool(true) is either of a or b are trueBehave as:
(a or b) => Value of a if true, b if a is not true but b is, bool(false)
otherwise.
(a and b) => Value of b if both are true, bool(false) otherwise.
Besides BC concerns, it is probably not a good idea to introduce (more) subtle differences between and
and &&
. That is, if you change and
, you should change &&
in the same direction.
Maybe simply introduce the !?:
operator?... Nevermind.
—Claude