Skip to content

RFC Proposal: ??= equal null coalesce operator #1795

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed

RFC Proposal: ??= equal null coalesce operator #1795

wants to merge 4 commits into from

Conversation

midorikocak
Copy link

Actually this was a question about null coalescing operator. Check this:

$this->request->data['comments']['user_id'] = $this->request->data['comments']['user_id'] ?? ‘value’;

I want to check if some var is null and if the same var is null set the same var to ‘value’.

Hence I am repeating the same variable after the equal operator, this does not feels right.

So I feel that we need another operator like “??=“ similar to +=;

$this->request->data['comments']['user_id’] ??= ‘value’. So if the var is null it’s set to ‘value’ and else stays the same.

In this pull request I tried to implement this.

$ sapi/cli/php -r '$num = null;$num ??= 5; echo $num;'
5

@midorikocak midorikocak changed the title null coalesce operator equal null coalesce operator Mar 8, 2016
@midorikocak midorikocak changed the title equal null coalesce operator RFC Proposal: equal null coalesce operator ??= Mar 8, 2016
@midorikocak midorikocak changed the title RFC Proposal: equal null coalesce operator ??= RFC Proposal: ??= equal null coalesce operator Mar 8, 2016
@Kubo2
Copy link
Contributor

Kubo2 commented Mar 8, 2016

Nice. I support the idea 👍

@Majkl578
Copy link
Contributor

Majkl578 commented Mar 9, 2016

If this is going to be proposed as RFC for php-next, I'd also like to see/propose a shortcut for ?: (?= maybe?), at least for the sake of consistency.

@laruence laruence added the RFC label Mar 9, 2016
@wilson208
Copy link

@midorikocak this is awesome! Well done!

@nikic
Copy link
Member

nikic commented Mar 9, 2016

Maybe I'm missing something here, but is $undef ??= $default (and it's variations $array['undef'] ??= $default and $object->undef ??= $default) handled correctly (i.e. without throwing a notice)?

@kenguest
Copy link
Contributor

kenguest commented Mar 9, 2016

@Majkl578 Call the shorthand version "elvis equals" perhaps? :) (cf https://en.wikipedia.org/wiki/Elvis_operator for those wondering about the Elvis reference)

Either which way, this is brilliant work!

@jerrygrey
Copy link

Definitely needs this! 👍👍

@fabiomsouto
Copy link
Contributor

+1

@ZurabWeb
Copy link

ZurabWeb commented Mar 9, 2016

+1 useful improvement

@emir
Copy link

emir commented Mar 9, 2016

+1

@midorikocak
Copy link
Author

Here is the RFC on PHP.net https://wiki.php.net/rfc/null_coalesce_equal_operator

@PHPmaker
Copy link

PHPmaker commented Mar 9, 2016

this is a amazing idea +1

On Thu, Mar 10, 2016 at 4:19 AM, Midori Koçak notifications@github.com
wrote:

Here is the RFC on PHP.net
https://wiki.php.net/rfc/null_coalesce_equal_operator


Reply to this email directly or view it on GitHub
#1795 (comment).

From:
Zion Paton
Owner of
Zion Web Applications

Email:
zionpaton@gmail.com
zion.pation@sniip.com zion@sniip.com

Phone:
Personal 0421 179 149
Professional 0414 603 536

Web Designer
Web Developer
Databasing
Intranet Development

@bwoebi
Copy link
Member

bwoebi commented Mar 9, 2016

@midorikocak Apart from the point that it should be semantically equivalent to ?? (like @nikic noted), you should introduce some tests for it.

@krakjoe
Copy link
Member

krakjoe commented Mar 9, 2016

bump ... what @nikic said ... (he isn't missing anything) ...

$ sapi/cli/php -r '$num ??= 5;'
PHP Notice:  Undefined variable: num in Command line code on line 1

then what @bwoebi said ...

zval op1_copy, op2_copy;
int converted = 0;

if(Z_TYPE_P(op1) == IS_NULL) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after if keyword.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks I will check that.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it wasn't checked

@midorikocak
Copy link
Author

@krakjoe Am I missing something? I don't get any errors or notices.

mtkocak@Midori-MacBook-Pro:~/src/php-src$ sapi/cli/php -r '$num ??= 5;'
mtkocak@Midori-MacBook-Pro:~/src/php-src$ 

@nikic
Copy link
Member

nikic commented Mar 10, 2016

@midorikocak Add -derror_reporting=E_ALL. By default notices are suppressed :(

@midorikocak
Copy link
Author

@nikic where can I change this for the compiled php in sapi dir?

@tpunt
Copy link
Contributor

tpunt commented Mar 10, 2016

@midorikocak It's a runtime setting: sapi/cli/php -derror_reporting=E_ALL -nr 'echo $a;'

@@ -1299,6 +1299,7 @@ static void zend_ast_export_ex(smart_str *str, zend_ast *ast, int priority, int
case ZEND_ASSIGN_SL: BINARY_OP(" <<= ", 90, 91, 90);
case ZEND_ASSIGN_SR: BINARY_OP(" >>= ", 90, 91, 90);
case ZEND_ASSIGN_CONCAT: BINARY_OP(" .= ", 90, 91, 90);
case ZEND_ASSIGN_COALESCE: BINARY_OP(" ??= ", 90, 91, 90);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,
insertion breaks other lines BINARY_OP alignment.

@ZurabWeb
Copy link

@midorikocak I still feel like it should be =?? instead of ??=, as this would make the most sense:

-------vvvv--------- < getting rid of the extra $var
$var = $var ?: null;
-----^------^------- < equals sign before question mark

$var =?? null; // this makes more sense
$var ??= null; // than this

The equals sign comes before the question mark and I would keep it that way to easier understand that this operation can potentially change the value of $var. The equals sign right after the var name is a great hint for these purposes.

I'm not sure if =?? might already be reserved for something else, however.

I love the idea though, so whichever way it gets implemented, no doubt it'll be very useful.

@nikic
Copy link
Member

nikic commented Mar 11, 2016

@ZurabWeb Compound assignment operators always have the operator on the left side, e.g. $a += $b is the same as $a = $a + $b, similarly to how $a ??= $b will be the same as $a = $a ?? $b.

@midorikocak There is some info about writing tests here: http://qa.php.net/write-test.php However it's probably easier to simply pick an arbitrary file in Zend/tests and follow it's structure :)

@bm-pzegardlo
Copy link

There will be a notice if variable on left side does not exists?

@ZurabWeb
Copy link

ZurabWeb commented Apr 1, 2016

@midorikocak needs a rebase.

@jrfnl
Copy link
Contributor

jrfnl commented Feb 17, 2017

Why was this PR closed ? AFAICS the RFC was approved and listed in the pending implementation list.

@Fleshgrinder
Copy link
Contributor

The author closed it, I guess she lost interest. Anyone should be allowed to take over I guess.

@dshafik
Copy link
Contributor

dshafik commented Feb 17, 2017

@saramg intended to take it over as there was an issue with this patch that was tricky to resolve.

@jrfnl
Copy link
Contributor

jrfnl commented Feb 23, 2017

@Fleshgrinder @dshafik Thanks for your response.

@sgolemon Care to give a status update ? 😍

@regularlabs
Copy link

If someone wants to give this another go:
It would be nice if both ?= and ??= would get implements.
So:
$var ?= 'foobar';
would be the same as:
$var = $var ?: 'foobar';

And:
$var['key'] ??= 'foobar';
would be the same as:
$var['key'] = $var['key'] ?? 'foobar';
which is of course the same as:
$var['key'] = isset($var['key']) ? $var['key'] : 'foobar';

@jfcherng
Copy link

jfcherng commented Oct 23, 2017

Wouldn't
$var = $var ?: 'foobar'; => $var ?:= 'foobar';
make the syntax more consistent than
$var ?= 'foobar';
?


?:= is ugly though.

@regularlabs
Copy link

regularlabs commented Oct 23, 2017

I don't agree with that.

In that logic, the ?? operator should be ??:.

@jfcherng
Copy link

jfcherng commented Oct 23, 2017

Why? There is no : in $var['key'] = $var['key'] ?? 'foobar';.

I think a compounded op is working like
$foo {OP}= $bar; => $foo = $foo {OP} $bar;

@etu
Copy link

etu commented Oct 23, 2017

@jfcherng
For once: ?? is it's own operation. ??= is based on ?? not ?:.

Also, this is not a thread for discussing the style or so. This discussion has already happened and has been accepted. This thread is for implementing what has been decided on.

@caseyfw
Copy link

caseyfw commented Dec 20, 2017

Why did this not make it into 7.2?

@Majkl578
Copy link
Contributor

Unfortunately the implementation was never finished/fixed...

@caseyfw
Copy link

caseyfw commented Dec 20, 2017

Guess I'll update the wiki page then - @lkmorlan may have jumped the gun a bit.

@the-liquid-metal
Copy link

$ugly = [];
$awesome = [];

foreach ($rows as $item) {
    $g = $item->group;
    if (isset($ugly[$g])) {
        $ugly[$g] += $item->qty;
    } else {
        $ugly[$g] = $item->qty;
    }
}

foreach ($rows as $item) {
    $g = $item->group;
    $awesome[$g] ??= 0;
    $awesome[$g] += $item->qty;
}

https://francescocirillo.com/pages/anti-if-campaign
https://code.joejag.com/2016/anti-if-the-missing-patterns.html
https://stackoverflow.com/questions/1167589/anti-if-campaign

@passcod
Copy link

passcod commented May 7, 2018

I'm interested in taking this up and finishing it, is there anything in particular missing apart from tests?

@dshafik
Copy link
Contributor

dshafik commented May 7, 2018

@passcod as per my comment here, @sgolemon was going to help with some edge cases, but I don't recall what those were.

I'm sure she'd be delighted for you to take the reins and get this completed.

@nikic
Copy link
Member

nikic commented May 7, 2018

@passcod Quoting part of a mail I wrote in one of the discussion threads for this feature:

The only statement the RFC essentially makes is that $a ??= $b will be the same as $a = $a ?? $b, for variable-expression $a and expression $b. This statement, while a good high-level illustration, does not explain the exact behavior of this operator.

For example, consider the expression $a[print 'X'] ??= $b. A simple desugaring into $a[print 'X'] = $a[print 'X'] ?? $b will result in 'X' being printed twice. However, this is not how all other existing compound assignment operators behave: They will print X only once, as the LHS is only evaluated once. I assume that ??= would behave the same way.

However, with ??= the problem becomes more complicated. Let us assume that $a is an ArrayAccess object and consider the expression $a[0] ??= $b. Let us further assume that $x = $a->offsetGet(0) is non-null. Will $a[0] ??= $b result in a call to $a->offsetSet(0, $x)? This is what would normally happen with a compound assignment operator and what would be implied by the desugaring $a[0] = $a[0] ?? $b. However this assignment is not really necessary, as we're just reassigning the same value. So, does the call happen or not? Is the proper desugaring maybe if (!isset($a[0])) $a[0] = $b?

Let us now assume that $a is a recursive ArrayAccess object with by-reference offsetGet() and consider the expression $a[0][1] ??= expr. For a normal compound assignment operator, this would issue the call sequence

$b = expr;
$x =& $a->offsetGet(0);
$y = $x->offsetGet(1);
$y OP= $b;
$x->offsetSet(1, $y);

Note that we only issue one offsetSet() at the end. We do not refetch $x via $a->offsetGet(0). How would the same work with the ??= operator? As the RHS is evaluated lazily, it is my opinion that only performing the offsetSet() call without refetching $x beforehand would violate PHP's indirection memory model. Additionally as ??= has to fetch offsets in BP_VAR_IS mode, we likely wouldn't be able to write them without refetching anymore.

So, what would be the desugared call sequence for $a[0][1] ??= expr? Something like this?

if (!$a->offsetHas(0)) {
    goto assign;
}
$x = $a->offsetGet(0);
if (x === null) {
    goto assign;
}
if (!$x->offsetHas(0)) {
    goto assign;
}
$y = $x->offsetGet(0);
if ($y === null) {
    goto assign;
}
goto done;
assign:
$b = expr;
$x =& $a->offsetGet(0);
$x->offsetSet(1, $b);
done:

That would be some first thoughts on the issue, though I'm sure there are more subtleties involved. I'd like to see the exact behavior of ??= (and ?:=) specified.

I'm also pretty sure that writing a patch for this will not be entirely easy. The combination of execute-once LHS side-effects and lazy RHS execution does not translate well to PHP's VM constraints.

The last line is the TL;DR, those are the issues that need to be solved. Additionally there is the question mentioned earlier where $a ??= $b is approximately $a = $a ?? $b or $a ?? $a = $b (the latter I believe has precedent in Ruby).

@passcod
Copy link

passcod commented May 7, 2018

Right, so the first order of business on this is to specify exactly the behaviour of the operator, which will then inform the patch (and its feasibility). I'll spend some time thinking about this.

Am I correct to assume that the best place to discuss what I come up with would be the mailing list, or would this pull request thread do?

@cmb69
Copy link
Member

cmb69 commented Jun 20, 2018

What's the status here? Note that feature freeze for PHP 7.3 is scheduled for July, 17th.

@passcod
Copy link

passcod commented Jun 20, 2018

Unless someone else has made progress, I don't think it will make it.

@the-liquid-metal
Copy link

the-liquid-metal commented Jul 1, 2018

$ugly = 0;
$awesome = null;

foreach ($rows as $i => $item) {
    if ($i == 0) {
        $ugly = $item;
    } else {
        $ugly += $item;
    }
}

foreach ($rows as $item) {
    $awesome ??= $item;
    $awesome += $item;
}

// or may be more complex:
foreach ($rows as $item) {
    $awesome ??= $item->baseLine;
    $awesome += $item->in - $item->out;
}


// note: if your code needs to be consumed by parser, write this:
/** @var int|null */
$awesome = null;


// or this:
/** @var ?int */
$awesome = null;

@rulatir
Copy link

rulatir commented Sep 30, 2018

I am a little worried that this is proposed as a syntactic sugar for expr = expr ?? compute() as opposed to expr ?? expr = compute() which is the correct idiom.

expr = expr ?? compute() - performs the assignment every time, which involves re-evaluating expr as assignable (i.e. hitting offsetSet() instead of offsetGet() if those come into play).

expr ?? expr = compute() - elides the assignment altogether if expr is already set.

(Note: I am puzzled that the latter idiom works as described given that ?? has higher precedence than =, but it definitely does work that way, perhaps that's what "right-associative" means?)

@passcod
Copy link

passcod commented Sep 30, 2018

Just to note (in case it wasn't obvious) that I'm no longer onto this. While I hoped to have enough time, it's a lot harder than initially envisioned, and now I'm full busy for the foreseeable future. Hopefully someone else can pick it up :)

@midorikocak
Copy link
Author

We should definitely repoen this one.

@midorikocak
Copy link
Author

I am thinking that we should limit the operator to simple data types instead of callables or resources that can change on runtime. We should flag an error.

@nikic
Copy link
Member

nikic commented Jan 16, 2019

I've created an implementation for ??= here: #3747 It's not pretty, but I think it covers all the edge cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.