-
Notifications
You must be signed in to change notification settings - Fork 209
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
Spread Collections #47
Comments
Do you think there is place in this proposal to also consider |
I don't think it's a good idea to silently discard null values. That might be the right thing for some APIs, but others may consider null values to be useful and meaningful. If the spread syntax always discarded them, it means you could never use a spread with those APIs. |
I meant perhaps something like null-aware operators combined with spread to ignore |
I guess an extension method/getter like |
Just keep in mind that when designing a spread operator it goes hand in hand with the syntactically identical rest operator which goes hand in hand with destructuring syntax ;) |
Yes, the proposal doesn't mention that, but it was designed with destructuring and rest parameters in mind. |
The proposal for this has now landed: https://github.com/dart-lang/language/blob/master/working/spread-collections/feature-specification.md |
AngularDart makes heavy use of const lists as part of our configuration for the template compiler. In order to support items stored in another const list, we allow a "recursive" union type: According to your proposal, Could this be adjusted to allow const elements to be spread in const lists? For example,
|
It seems to me that there is a missing option in the discussion about the null aware spread operator. In the the postfix version could the [foo?.bar?...]
// ^ | ^ |
// '-' '---' In my eyes this seems more consistent with the existing null aware operator since the question mark appears directly after the value being checked for null. |
I don't see this as being so burdensome that we should be trying to get a operator that does it automatically - and something doing it automatically could be very confusing to people who do want to preserve nulls in there. |
Not easily, unfortunately. Consider: class InfiniteSequence extends ListBase<int> {
const InfiniteSequence();
Iterator<int> get iterator {
return () sync* {
var i = 0;
while (true) yield i ++;
}();
}
}
const List<int> things = InfiniteSequence();
const forever = [...things]; The static type system has no way of knowing Const is just really annoying and limited in Dart, unfortunately.
Yes, that's an option too. But, in general, I and the language leads prefer the prefix syntax.
Dart doesn't have objects in the JS sense, just maps, so there shouldn't be too much room for confusion.
Yup, you can spread any object that implements Iterable. The |
We actually could do this, I think, but it adds another annoying special case to consts. Basically you just say that spreads can appear in const lists if the target of the spread evaluates to a const element of the builtin list type. |
Const is nothing but a collection of annoying special cases. :)
Oh, right, because we do have access to the actual const value at compile time. That's a good point. Do you think it's worth adding to the proposal? I'm agnostic since I basically never use const anyway. |
How about opening an issue for discussion and feedback of this (link it here)? |
Done! If you'd like to talk about const spreads, go here: #63. |
The list-spread feels similar to string-interpolation: I'm writing a literal X, and I want to embed another X in it. I think it makes perfect sense from that perspective. You can introduce a computed sequence by using IAFLs (Immediately Applied Function Literals): var list = [
something,
other,
... () sync* {
if (someTest) yield someValue;
if (otherTest) yield* otherValues;
List complexList;
complexComputationPopulatingList;
yield* complexList;
} (),
whatnot
]; If that use-case is common, doing complex computation in the middle of building a literal, we could perhaps introduce a shorthand for it, to avoid the |
How about spreading a map into a constructor? to help cleanup long Flutter widget build methods. |
What would the result be of |
Or |
That is a much harder problem because it interacts with the static type system. A map is homogeneously-typed in Dart. A set of named arguments are not. For named arguments, what you really need is something more like a [record type](https://en.wikipedia.org/wiki/Record_(computer_science\)). If we had rest parameters, we could support spreading to those in a fairly straightforward manner. This proposal includes that. However, rest parameters are a big complex feature because they affect the function calling convention. This proposal is much simpler since the compiler can desugar the spread to a set of operations on a list or map.
The |
This should however be possible with classes instead of maps right? class Foo {
String foo;
}
function({ String foo }) {}
/* TEST */
final Foo foo;
function(...foo); |
Ah, interesting. Yes, in theory we could treat the getters on a class as defining an ad-hoc record type and use that to destructure to the named parameters. That feels pretty dubious to me. If we're going to start supporting destructuring for classes, I think class authors should have more control over how that destructuring works. |
I think this is a pretty important aspect of the destructuring as Flutter uses mostly named parameters and classes properties.
Using JS as inspiration we could have the following: class Foo extends StatelessWidget {
final int omitted;
final int foo;
final int bar;
build() {
final { omitted, ...other} = this;
return Widget(
...other,
);
}
} |
I highly prefer prefix syntax; |
There is another potential solution. One thing we've discussed is making the null-aware operators short circuit. It's stupid and pointless that once you use one null-aware operator, you have to use them for the rest of the method chain. Code like this is always wrong: foo?.bar.baz().bang(); You have to write: foo?.bar?.baz()?.bang(); The obvious fix (which C# has always done) is to short-circuit the rest of the method chain if the LHS of a null-aware operator returns null. That would make the first example behave like the latter. If we do that, we could also make [...?foo?.bar()] Instead, it would be: [...?foo.bar()] And the reader knows the first |
foo?.bar.baz().bang(); While I like the idea, it faces the same issue then with allowing It also makes no sense to allow Alternatively, it is a breaking change. |
I think making [...? foo.bar.baz()] Should this check whether (I'd also use a space after the |
I probably speaks too much, but: Would cascade make sense with spread too? List<String> foo = [
"before",
Foo()
...bar
...baz,
"after",
]; which would be equal to Foo tmp = Foo();
List<String> foo = [ "before"]
..addAll(tmp.bar)
..addAll(tmp.baz)
..add("after"); This means that instead of ...?foo?.bar we could do foo?...bar |
On further thought, I agree. Perhaps the right way to handle this is that if we add general-purpose support for short-circuiting to [...foo?.bar()] So you'd never need |
Generalizing wildly, we can allow Then you can write: var x = null;
!?x; // null or boolean negate if not null
-?x; // null or negate
x ?+? x; // check both for null, otherwise add?
x?++; // null or increment.
x?[42]; // null or index Then It's ... unlikely to be readable in general, though, and will likely clash with the conditional expression in some way. (It's also unclear whether |
The problem is that
Conditional expression |
var wat = { x ? [42] : y }; Does this create a map or a set? :) |
As I said: "wildly" :) The So, still speculating wildly, I think I have a solution for extending null-awareness of the receiver to operators, what we lack is null-awareness of the other operands. One (again quite overloaded) option is to allow It's not symmetric. It doesn't generalize to operator operands (unless we go |
// Just for reference, check out Swift's operator creation functionality... very powerful. |
Very secondary question but why ... instead of *.
So in my mind * is binded to sequence and then when spreading it goes to ... that is the JS expression. Is there any reason for having two symbols for the same concept of sequence ? |
The var list = [
something,
someLongValue
* example;
]; Here I forgot the I can see that So, in short, the |
Rationale: Implementation of control-flow collections is done through the notion of block expressions. This is a first implementation to get this working. It takes a few shortcuts (like disabling OSR inside block expressions for now) that may be refined later. dart-lang/language#78 dart-lang/language#47 Change-Id: I966bf10942075052fcfd9bac00298a179efc551b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/94441 Reviewed-by: Alexander Markov <alexmarkov@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
Closing; this is launching in Dart 2.3 |
Solution for #46.
Feature specification.
Most other languages have a "spread" or "splat" syntax that interpolates the elements of an existing collection into a new collection. In JS, it's a prefix
...
. In Ruby, Python, and a couple of others, it's a prefix*
.I propose we follow JS and use
...
.This proposal is now accepted. Implementation work is being tracked in #164.
The text was updated successfully, but these errors were encountered: