-
Notifications
You must be signed in to change notification settings - Fork 207
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
Should null-aware subscripting use ?[
or ?.[
syntax?
#376
Comments
var wat = { a ? [b] : c }; Is this a set literal containing the result of a conditional expression, or a map literal containing the result of a null-aware subscript? I think we'll probably want to do |
I believe the suggestion that @bwilkerson made was that var set = { a ? [b] : c }; // Set literal
var map = { a?[b] : c}; // Map literal Note that Swift and C# both use var x : Array<String>?;
var t1 : String? = x?[0]; // Treated as a subscript
var t2 : String? = x? [0]; // Treated as a subscript
var t3 : Array<Int>? = x == nil ? [0] : [3]; //Treated as a conditional expression
var t4 : Array<Int>? = x == nil ?[0] : [3]; //Treated as a conditional expression |
Swift does seem to use tokenization to distinguish, it's just whether there is a space between the Whether to trigger on var v1 = longName?
[longExpression];
var v2 = longName
?[longExpression];
var v3 = longName?[
longExpression]; I can't see any one to prefer. So, what do we do for var v4 = longName
?.longName(); That does suggests that we want C# does not have the issue because |
I think the question mark aways close to the variable as subscript is better to read. |
Leaf and I spent some time talking about this at the whiteboard. My take on it going in is that both options have some things going for them:
We spent a while trying to come up with ways to avoid the ambiguity with We talked about eventually adding null-aware forms for other operators: Another addition we have discussed for NNBD is a null-aware call syntax. If we don't require a dot there, it has the exact same ambiguity problem: var wat = { foo?(bar):baz }; // Map or set? So whatever fix we come up with for the Finally, Leaf wrote up an example of chaining the subscript: foo()?[4]?[5] To both of us, that actually doesn't look that good. It scans less like a method chain and more like some combination of infix operators. A little like foo()?.[4]?.[5] Here, it's more clearly a method chain. Communicating that visually is important too, because users need to quickly understand how much of an expression will get null-short-circuited. Putting all of that together, it seems like the
So we're both leaning towards |
@lrhn I'm going to close this in favor of |
LGTM. I did not find the white-space based disambiguation tecniques convincing, they didn't fit well with the current Dart syntax, and I couldn't see any other reasonable way to disambiguate. |
Question: was any consideration given to the syntax This is simpler to remember (IMO) than |
The (On the other hand, that could be a useful functionality by itself: If a function parameter or index operand starts with |
@lrhn wrote:
When that idea was discussed previously, the main concern was that it would be hard to read: var x = ui.window.render((ui.SceneBuilder()
..pushClipRect(physicalBounds)
..addPicture(ui.Offset.zero, ?picture)
..pop())
.build()),
}; How much of the above should be shorted away when the var x = let ?thePicture = picture in e; This would cancel the evaluation of But I agree that a null-shorting semantics for |
Personally, I prefer |
Definitely |
I am in favor of |
I find the arguments for
It behaves differently from cascading (types of the expression are type of
I'd say it doesn't mirror this. Call syntax is
I found white space matters in Dart for: --, ++, &&, ||, !=, ==. Which are some very common operators, hardly a "corner".
But it is not a method chain, why should it look like one? In my opinion, the syntax should be consistent (adding a single |
(Edit: Kathy said this all this better already: https://medium.com/dartlang/dart-nullability-syntax-decision-a-b-or-a-b-d827259e34a3) The current plan is to go with A null aware cascade will be This syntax parses without any ambiguity, whether we require The alternative proposed here is to use This does not parse unambiguously if If we treat Currently you can write The If we use So, the arguments against |
@lrhn For clarity: Maybe too subtle of a difference, but my argument isn't about look better, but about consistency: If a user knows that |
Just a few semi on topic thoughts... Comparing to other languages I can think of a couple of sticking points using "?." for optional chaining ( although neither of these cases actually clash with the proposed null aware subscripting operator ): Implicit member expressions in Swift let color: UIColor = condition ? .red : .blue Numbers in JS, without the integer component let value = condition ? .1 : .2 Referring to the "?[" option, I feel like the it would be possible to parse and differentiate from a ternary conditional. Do a speculative expression parse after a detected "?" token and then check if the token following the expression is a ":". It requires that you can rewind the token stream to a point prior to the speculative expression parse, and that if it failed there would be no side effects. I don't know enough about the structure of the Dart scanner/parser to say how feasible that it is but it seems like a lot of potential work. In terms of plain personal preference I think I'd prefer |
Sorry, my initial reaction is to support I think the decision to go with I think the main selling point of The second strongest argument, the congruence with cascade syntax is not that convincing to me either, because it's easier to remember "you always add a question mark after the nullable" rather than "you also have to add a dot after the question mark, because it has to look similar to cascade (which is not what we are using here, but it needs to look similar)". The dot feels like it came out of nowhere. The third, chaining: if I am chaining an operator like this, I am probably already treading lightly that I might be making a mistake somewhere. If my life depends on it, I am probably using a number of Either case, thank you for introducing non-nullable types! It's a huge step forwards and I won't really mind the final decision here 😄. |
Hmm. I have a feeling that Dart is worry about this because it is used heavily by Flutter which is heavily used with Firebase. NoSQL engines and non-existent fields are so common it isn't even funny. If NoSQL is going to persuade your choice, please make it apparent that it is a key use case. Not saying it is, but if it is, I'd like to know. I am having second thoughts against Here's an idea: build a survey with code snippets paired with potential results. Ask the user what they think they do. Let the results guide you. If it simply gets too confusing to use in any scenario (ex: |
Maybe |
How about a superset symbol? It implies that the left is a superset of the right. Empty anything isn't really a superset of anything, so it would work, right? :
|
Fair point. What I had in mind is that it mirrors treating The idea here is that if we were to do that, then using
It is a method chain. The
Are you sure about that? If I run: main() {
String foo = null;
print(foo ? . length);
} I get compile errors.
The formatter already handles splitting on null-aware method chains and it keeps
Yeah, unfortunately using |
If we must use additional un-ambiguity characters maybe we can consider |
@morisk "All the good syntaxes are taken!" |
Right, point well taken. |
Oh, I guess I hadn't thought this through. @Cat-sushi , thanks for pointing it out. I'm sorry, but I no longer understand the position of the dart team here. @eernstg says:
@munificent says:
So, what is it going to be? If you have managed to make NNBD a seamless transition where existing code doesn't have to be touched, then I understand the necessity of |
It will not break existing code when imported libraries switch over to NNBD, but if you want to port your own code to NNBD then it is very likely that various parts of it needs to be updated. |
It sounds as if you will basically have two 'compilation modes'. If your code doesn't contain any NNBD syntax, it is compiled with the legacy mode. Then as soon as you start using the NNBD syntax, the compiler expects consistency and you will have to adapt your code. In that scenario, why would it be a problem to change the ternary operator? Dart with and without NNBD code is handled separately in any case, with breaking changes to the syntax. I always feel like there are times for discussion and times for sticking with a decision and making it work. It seems to me that I'm late to this issue and that the decision has been made sometime in spring. Please excuse me if I'm wasting your time here. I do very much appreciate the efforts of the dart team and their push towards NNBD.
Well now it sounds like managing this migration is really no problem at all. Millions of lines of code where you have to think about the proper new type for your |
Language versioning makes it possible to indicate that a particular library is at a specific level. This can be used to say that the library isn't yet ready for nnbd. It is possible to have opted-in as well as opted-out libraries in the same program. You could consider this to be two modes, but they are not permanent, they are just used to get from a completely pre-nnbd state to a completely nnbd state without forcing everyone to do it at the same time. This transition has been used to introduce a lot of breaking changes (e.g., PS: I'll stay out of the discussion about ternary operators. ;-) |
Yes, we could unquestionably tack this onto the NNBD release (or any other large opt-in breaking change that we manage via language versioning). Pragmatically, even if we had consensus on this now and had all of the technical details worked out (I'm not sure we do) I don't think it's feasible to add this to the task list for the NNBD release at this point. I'm highly sympathetic to the desire to FIX ALL THE THINGS NOW, but we really need to ship NNBD and move forward. |
By the way - as a meta-level comment, I really appreciate both the style and the content of the discussion here. There are a lot of insightful comments, and some good "outside of the box" suggestions that I've found useful to think through. So thanks for that! |
Right. You can think of the Dart SDK as simultaneously supporting two separate languages "legacy Dart" and "NNBD Dart". You can write your code in either language and it will run both of them just fine. Your program can even be a mixture. Sort of like "strict mode" in JS.
We don't implicitly opt you in to the new NNBD flavor of Dart by detecting your attempt to use it. Instead, you have to opt in your package by updating the SDK constraint in your pubspec to require a version of Dart that supports NNBD. But, otherwise, you have the right idea. We don't consider this a breaking change because your existing code keeps working just as it does today. If you want to use the NNBD features, you have to opt in to NNBD and that may require you to change other parts of your program. But that's something you choose to do when you want to choose to do it. We don't break your code. |
AFAIK, the migration period during which legacy code without NNBD opted in and new code with NNBD opted in can run simultaneously is finite, and the Dart team will encourage all the developers to modify all their pieces of code especially those depended by others into those migrated with NNBD opted in, as soon as possible, in order to take full advantage of null safety. Because null safety even in code with NNBD opted in would berak around the border of legacy code, and also because the SDK can't make optimization which should be made in applications with NNBD fully opted in. In addition, at some point after the migration period, I guess the SDK will drop legacy mode, just like the SDK dropped the mode which could be called "Weak Mode" at Dart 2.0, in order to make the SDK simple again. At that point, the lifetime of applications and libraries which have decided not to be migrated will end. Needless to say, removal of current syntax of conditional expression |
The language team met this morning, and we spent some time reviewing this issue. There are no concrete changes in the outcome at this point, discussion is ongoing. Here's my quick summary of the discussion points.
|
|
|
Good point. |
I didn’t comment further, because I felt like I had voiced my opinion and didn’t have anything new to contribute. But now curiosity is taking over :P. Is there some progress regarding this discussion? Also, I would like to say that I appreciate how much you respond to the community. Thank you for investing the time to go over this again. Even if the outcome doesn’t change, it feels like you very much valued our feedback. Happy new year :) |
I hope to have a decision one way or the other very shortly.
Thanks! And as I said above, we very much appreciate the high quality feedback we've received, and the thoughtful and respectful tone of the discussion. Happy New Year to all you language enthusiasts out there! :) |
TL:DR: We're doing OK, friends! I want to thank all of you for all of the very very helpful feedback. A clear take-away from this thread was that almost everyone prefers the The question remaining for us was, if we were to do The mechanism we'll use to resolve the ambiguity is basically, "if it is syntactically a valid conditional expression, then it is parsed as one". So in this example: var what = { a?[b]:c }; The Conditional expressions are always preferred even in cases that technically aren't ambiguous. So here: var mustBeMap = <int, String>{ a?[b]:c }; In this case, the The goal here is to avoid parsers and—more importantly—human readers needing to take into account too much surrounding context in order to read a piece of code. We don't want you to have to scan back and say "Oh, this must be a map literal, so actually even though it does kind of look like a conditional expression, it's not." In practice, what this means is if you don't want a conditional expression, you need to parenthesize the key: var mustBeMap = { (a?[b]):c }; I think cases where this comes into play are likely to be very rare anyway. A null-aware index operator can evaluate to The nice thing about this approach is that it doesn't make parsing whitespace sensitive. That means you can still throw unformatted code that hits this ambiguous case at dartfmt and it will be able to make sense of it. When we started this thread, I honestly expected most people wouldn't care one way or the other and those who preferred I'm going to go ahead and close this issue now since we've reached a decision (and one I believe most of you will be happy with), but do feel free to comment if you have further thoughts and if need be we can reopen. |
@leafpetersen I still see |
Can't wait for this to be out, I rather work with default than extensions like
Was trying to replicate kotlin, so I can do someMap?.getValue("key"), besides in languages like kotlin a nullable type cannot access index with [] has to be ?.someFetchMethod() |
¿Any date or Dart version where this is gonna be implemented, guys? |
This is part of the upcoming null-safety feature set. For instance, you can run the following on https://nullsafety.dartpad.dev/: class A {
Object operator [](int i) => true;
operator []=(int i, Object? o) {
print("Setting $o");
}
}
main() {
A? a = A();
var b = a?[0];
a?[0] = b;
} |
Thank you very much! |
is this supposed to work on dartpad now ?
|
@aktxyz No, use nullsafety.dartpad.dev for null safe code until null safety is released (and dartpad updated). |
Even at nullsafety.dartpad.dev, that will cause a warning, because |
The draft proposal currently extends the grammar of selectors to allow null-aware subscripting using the syntax
e1?.[e2]
, however we've had some e-mail discussions about possibly changing this toe1?[e2]
, which would be more intuitive but might be more difficult to parse unambiguously.Which syntax do we want to go with?
The text was updated successfully, but these errors were encountered: