gbracha / nullAwareOperators

Proposal for null-aware operators in Dart
Apache License 2.0
12 stars 2 forks source link

clarification on semantics of ??= #11

Open sigmundch opened 9 years ago

sigmundch commented 9 years ago

Question for @gbracha and @stereotype441

The proposal says that the semantics of a ??= b are the same as:

  a = a ?? b

This means, a will be assigned it's own value when it's not null. This can be observable if you do:

get v => 1
set v(value) { print('setter was called with $value');
main() {
  v ??= 2; // prints "setter was called with 1"
  v ??= 3; // prints "setter was called with 1"
}

However, some tests are written with a different semantics that avoids the assignment if the value is not null. In other words:

  ((v) => v == null ? a = b : v)(a)

I currently have the former implemented in dart2js and I'm inclined to change those tests to match, but I wanted to verify this was the intended behavior.

stereotype441 commented 9 years ago

I raised this question with Gilad in March, and I believe that is what prompted him to change the semantics from a = a ?? b to ((v) => v == null ? a = b : v)(a). (Note that the spec changes that were made in https://codereview.chromium.org//1031323002 are consistent with the tests). So yes, it was a deliberate change and my preference would be to keep it the way it's currently spec'ed (and tested).

For context, here's the line of reasoning that motivated me to suggest the change:

sigmundch commented 9 years ago

Thanks for the detailed answer Paul - and thanks for the link to the spec changes! I was only aware of the proposal in this repo, I didn't know the changes were already added to the actual spec, I'll take a look there as well.

Regarding:

which has the very odd semantics that if a is null, then c is evaluated and then its result is thrown away!

Not sure I follow this, given that a?.b = exp semantics are:

((x) => x == null ? null : x.b = exp)(a)

It seems that desugarizing a ??= b as a = a ?? b should yield the same result in this case:

((x) => x == null ? null : x.b = a?.b ?? c)(a)
sigmundch commented 9 years ago

Follow up question: how many times do we print read in this code?

var _x = new A();
get x {
  print('read');
  return _x;
}

main() {
  x?.y ??= 3;
}

It seems like under the old semantics we'd print it twice:

x?.y ??= 3

// is equivalent to:
x?.y = x?.y ?? 3

// which corresponds to:
t1 = x;  // (x in the lhs, prints "read")
if (t1 != null) {
  t2 = x; // (x in the rhs, prints "read")
  if (t2 != null) {
   t3 = t2.y;
  }
  if (t3 == null) t3 = 3;
  t1.y = t3;
}

It appears that under the new semantics we print it 3 times:

x?.y ??= 3
// expands to:
t1 = x?.y;  // prints "read"
if (t1 == null) {
  x?.y = x?.y ?? 3 // prints read twice like above.
}

is that correct?

stereotype441 commented 9 years ago

Regarding a?.b ??= c, you are right; please ignore what I previously said about this case.

Regarding the question of how many times "read" should be printed in the expression x?.y ??= 3, it should only be printed once. Using the equivalent expression in the spec (https://codereview.chromium.org/1031323002/diff/1/docs/language/dartLangSpec.tex#newcode4481):

x?.y ??= 3
// expands to:
((z) => z == null ? null : z.y ??= 3)(x)

(to avoid confusion I used z instead of x for the parameter in the function literal). This means that the getter for x is only called once, prior to invoking the function expression.

In general all the null-aware operators should evaluate subexpressions once if necessary, and zero times otherwise. Otherwise subexpressions with side effects will behave unexpectedly. Consider that a user might want to do myList[i++]?.y ??= 3, in which case they expect i to be incremented exactly once. I believe all the equivalent expressions in the spec achieve this; if you find one that doesn't I'd love to hear about it.

stereotype441 commented 9 years ago

Sorry I didn't point you to the spec changes earlier. I've updated the tracking bugs dartbug.com/23454 and dartbug.com/23455 to point to the spec changes so that hopefully Matthias won't fall into the same trap.

sigmundch commented 9 years ago

Thanks Paul!