Open alorenzen opened 5 years ago
In the short term, you can nest the if
inside the for
:
var result = [
for (var node in nodes) if (node != null) convert(node)
]
@bwilkerson I want to check the value of convert(node)
, not node
for nullability. You're example would require me to call convert
twice, which I do not want to do:
var result = [
for (var node in nodes) if (convert(node) != null) convert(node)
]
cc @munificent
This is an interesting corner. We could do some kind of single-value null-aware element syntax like you suggest. I'm very hesitant to pile more semantics onto ?
because it's already quite overloaded in the grammar (postfix for nullable types, in null-aware operators, and in conditional expressions).
In many cases, you can use the other UI-as-code features like Brian suggests, but in this case you want to avoid any redundant computation. Of course, the natural way to do that is with a local variable, which makes me think building the list imperatively like you do today probably is the best approach.
We could introduce a let
construct.
[ let tmp = something in if (tmp != null) tmp ]
which is obviously not as short as [? something]
, but it is more generally useful.
The current existing workaround is
[ for (var tmp in [something]) if (tmp != null) tmp ]
or
[... [something].where(isNotNull)]
or (after NNBD)
[... [something].whereType<Object>()]
All of these will likely introduce an intermediate list, with only the first example being likely to have that optimized away.
I think the only difference is that in #219, you propose allowing ?
to elide arguments in argument lists as well. Otherwise, I think they're the same.
Just saw code of the form:
List<Property<Foo>> get elements => [
something,
other,
orWhatnot,
andYetOne,
andFinally,
].whereNotNull().toList();
I tried to suggest using a literal with [if (something != null) something, ...]
, to avoid creating two lists where only one is needed, but the names were fields, so promotion wouldn't work. With null-aware elements, it would literally just be:
List<Property<Foo>> get elements => [
?something,
?other,
?orWhatnot,
?andYetOne,
?andFinally,
];
That would be awesome!
Agreed! Null-aware spread has spoiled me. We have a fancy null-aware element, but not a simple one.
Nullable could be updated to implement the Iterable
interface yielding either 0 or 1 items. Then we could just use the spread operator.
int? a = null;
int? b= 5;
final items = [
...a,
...b
];
// items is [5]
That's a neat trick! :smiley:
But I'm afraid it requires every type to implement iterator
(and a bunch of other methods), such that the null object can be the empty iterable, and every other object can be a one-element iterable, and that would not work. For instance, this creates a conflict for an object which is already an iterable yielding a bunch of other objects.
If we had Nullable<T>
as a type, shorthanded as T?
, then we could assign methods to that. (C# has such a type, although it's probably a very special system type).
Or if we could iterate based on an extension get iterator
, then we could assign one to T?
only. Might surprise someone when their Iterable<X>? f; something; [...f]
will give them a list containing a single iterable, not spread the iterable itself.
True, it will apply to all the non-nullable subtypes which have no iterator
themselves, which could be considered error-prone.
But I'm afraid it requires every type to implement iterator (and a bunch of other methods), such that the null object can be the empty iterable, and every other object can be a one-element iterable, and that would not work. For instance, this creates a conflict for an object which is already an iterable yielding a bunch of other objects.
Interesting! Didn't realize that nullables were implemented without a wrapper object. I have always wondered why e.g. int??
isn't a type. Now I know. Sigh...I wish we had first class discriminated unions.
In case you're curious, I wrote a blog post a while back about the rationale behind that design choice: https://medium.com/dartlang/why-nullable-types-7dd93c28c87a
The current existing workaround is
[ for (var tmp in [something]) if (tmp != null) tmp ]
... All of these will likely introduce an intermediate list, with only the first example being likely to have that optimized away.
Some time ago I took a shot at getting dart2js to optimize using a singleton list as a 'let'-expression and it was surprisingly difficult to get anything reasonable. I did manage to remove the allocation, but there were all kinds of weird artifacts in the generated that would not exist with a 'let'-expression. I would say that "being likely to have that optimized away" is too optimistic.
This should work for maps too, like:
final String? seasonId = '1';
final queryParameters = <String, Object>{
'seasonId': ?seasonId, // the '?' could instead go in front of the key, instead of the value
};
If Dart allowed for inferring the key, it would look better:
final queryParameters = <String, Object>{
?seasonId,
};
Then again, now that I'm reading it, maybe Dart shouldn't allow for inferring the key, looks weird 😅.
FWIW patterns help with this - the example in the original post could be written as:
var result = [
for (var node in nodes)
if (convert(node) case var converted?)
converted
];
I just added an in-progress proposal for this: https://github.com/dart-lang/language/commit/329f626a9bae65585065471d1cc59e236d7cf58b
Thanks again for the excellent suggestion @alorenzen!
Looks great!
I know this is a separate issue. but would it be possible to extend this to optional parameters as well?
final padding = isMobile ? null : EdgeInsets.all(6);
return Container(
padding: ?padding,
child: Text("This may or may not have padding"),
);
Argument elements is a different issue. It will likely need/want more than just null-aware arguments. At least if
-arguments to.
And then there is the complication of omitting a non-trailing positional argument.
And then there is the complication of omitting a non-trailing positional argument.
This is an intractable problem (semantic paradox), it cannot be solved without some restrictions, e.g. prohibiting conditionally omitting positional arguments. In the proposed null-aware operator in collections, ?value
doesn't move the insertion position if the value is null; while passing optional parameters this attitude might be counterintuitive.
(Edited by removing some nuts)
I would love to have a "null-aware" operator for adding an element to a list if it is non-null.
This would be equivalent to:
I've been trying out the new "UI-as-code" features in the angular codebase, and find that I can't convert a common pattern to use the new element syntax.
We often iterate over a list, adding elements to a new list when the value is non-null.
It would be pretty nice if I could instead use the new syntax: