Skip to content
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

Dart should have convenient syntax for creating (constant) sets. #36

Closed
lrhn opened this issue Oct 8, 2018 · 20 comments
Closed

Dart should have convenient syntax for creating (constant) sets. #36

lrhn opened this issue Oct 8, 2018 · 20 comments
Assignees
Labels
enhanced-const Requests or proposals about enhanced constant expressions request Requests to resolve a particular developer problem

Comments

@lrhn
Copy link
Member

lrhn commented Oct 8, 2018

We have decided to move forward with #37 as a solution to this issue.

Dart has list literals and map literals, but no set literals.
Sometimes a set is exactly the type you want (an efficient contains check), but there is no nice syntax for creating a set, and especially no way to make a constant set.

For APIs that take sets as arguments, this prevents them from having const constructors.

(The workaround would be to have an inefficient, or lazily built, set constructor based on a list literal: const ConstSet([1, 2, 3]), or a set constructor based on map keys: const ConstSet({1: 0, 2: 0, 3: 0}). Neither is very usable or readable, and if they are at all reasonable, they can also be used as the implementation of a language-based set literal).

Proposed solutions:

@lrhn lrhn added the request Requests to resolve a particular developer problem label Oct 8, 2018
@lrhn lrhn self-assigned this Oct 8, 2018
@lrhn lrhn mentioned this issue Oct 8, 2018
@munificent
Copy link
Member

Puts on pedantic hat.

This issue really is a description of a solution (set literal syntax) and not a problem ("no way to create const sets").

@matanlurey
Copy link
Contributor

matanlurey commented Oct 9, 2018

If we had variadic arguments, and could write const Set(n0, n1, n2, ...), I think the presence/lack of a literal syntax for it is a lower priority (and can be bike-shedded accordingly):

const set = Set('Lasse', 'Bob', 'Leaf');

For literals, I'd even be fine with re-using the list-literal syntax:

const set = Set['Lasse', 'Bob', 'Leaf'];

@yjbanov
Copy link

yjbanov commented Oct 9, 2018

This issue really is a description of a solution (set literal syntax) and not a problem ("no way to create const sets").

I'd argue that even lack of const sets is not the problem.

@zoechi
Copy link

zoechi commented Oct 10, 2018

I'd prefer if just const set = Set(['Lasse', 'Bob', 'Leaf']); would work without introducing any new syntax.

@lrhn lrhn changed the title Dart should have set literals. Dart should have convenient syntax for creating (constant) sets. Oct 10, 2018
@lrhn
Copy link
Member Author

lrhn commented Oct 10, 2018

The Set([1, 2, 3]) syntax requires magic to be efficient. It looks like it would create a constant list, then call a constant constructor, which can do nothing with that list except store it. Without special compiler magic, the set would not be efficient. Would that magic also apply to non-literal list arguments?
We try to avoid special compiler magic, especially where the user needs to remember rules for when it applies and when it doesn't.

If we have rest arguments, then const Set(E... elements) is a valid const constructor signature, but again, at its default meaning it creates a constant list, not a set, so we still need compiler magic. It is limited magic, though, since nobody else can ever see that list.

Special syntax is the most fundamental kind of compiler magic (aka. "the language"), so a magic Set constructor could be incorporated into the language at least as easily, and it would not require new syntax, which is a limited resource. It's definitely a possibility.
That is, something like:

A constructor invocation expression of the form Set<type>(e1, ..., en) where Set denotes the Set class declared in the platform libraries and n > 0, is treated specially, and not as an invocation of that constructor (which is declared as taking zero positional arguments). It evaluates as follows ... (describing the same semantics that a set literal would have).

It can be confusing to use the same syntax for non-similar things (if we get spreads in collection literals, but not in function arguments, would we have spreads in "set literals"?), but that argument also applies to using {..} for sets, maps and blocks. Even if it really is the perfect syntax for sets.

The Set[1, 2, 3] syntax is intriguing. It can generalize to other kinds of iterables, like Iterable[1, 2, 3] or Queue[1, 2, 3]. Not sure how to do that, it might require the type in question to implement a specific builder interface (have an "add" and "addAll" member), and it won't generalize to const literals of non-platform types. (We'll definitely start seeing Foo[e1, e2, e3] as a shorthand for variadic functions if we don't add that first).

Pedantry: Well spotted. Changed title.

@eernstg
Copy link
Member

eernstg commented Oct 10, 2018

Maybe we could allow forms like Set(1, 2, 3) and Set<num>(1, 2, 3) as a special case even before we add rest arguments, because we can ensure that it can be considered as a regular usage of Set(...E e) if/when we get support for rest arguments. So: Special exception today, regular thing tomorrow, and in any case a syntax which allows some room for generalization to other cases and avoids the ambiguities of {}.

@yjbanov
Copy link

yjbanov commented Oct 10, 2018

What is the ROI for this feature? Is the syntax change worth it if all we're trying to do is fix a handful of call sites?

@eernstg
Copy link
Member

eernstg commented Oct 10, 2018

There are no constant sets today, that's certainly a thing that you couldn't achieve without a language change, and that might be quite convenient if you want to use a set in a tight loop and the alternative is otherwise to (1) create a suitable set at top level or as a static variable and then use it only in that loop, or (2) create the set dynamically each time round in the loop.

@matanlurey
Copy link
Contributor

@eernstg:

and that might be quite convenient if you want to use a set in a tight loop...

I think I've seen @munificent or @lrhn mention having static local variables for this purpose?

tightLoop() {
  static someSet = createSet();
  while(someSet.contains(nextToken())) {
    // ...
  }
}

@yjbanov
Copy link

yjbanov commented Oct 10, 2018

@eernstg these are good hypothetical situations where it might be useful, but they are hypothetical. Do we have data showing that this change will have high enough impact to spend time on it right now? My intuition tells me there are areas with much greater impact than set syntax.

@yjbanov
Copy link

yjbanov commented Oct 10, 2018

For the record, my 👎 for this request should be interpreted as "we should not work on this now due to relatively low impact". The feature is great otherwise.

@Hixie
Copy link

Hixie commented Oct 10, 2018

FWIW, not having this feature has definitely been felt in Flutter land. We've designed entire APIs around the lack of this feature (e.g. the text decoration styles).

It looks like it would create a constant list, then call a constant constructor, which can do nothing with that list except store it.

We could also extend the definition of "const" such that it could do something with it... (my ideal long-term solution here is that all code could be "const" except for code that has side-effects beyond allocating memory, like code that references globals, or that does I/O.)

@lrhn
Copy link
Member Author

lrhn commented Oct 17, 2018

There is currently one proposal. Unless we find a better one, it will win by default.

The disadvantage of #37 is the complexity for the edge case where we need to use the context type to distinguish the possible meanings of {}, and that complexity might rise if we add syntax like spread operators or conditional elements to the language. It also uses prime syntactic real-estate (braces!) which are already overloaded for blocks and maps.

The only realist alternative suggested so far is to use Set(e1, ..., en) and const Set(c1, ..., cn) as literal syntax when Set refers to the built-in Set class of the platform libraries. Since the Set constructor accepts no arguments, this is currently a compile-time error, so it's a safe extension to recognize the syntax and assign a different meaning to it.

The primary argument against this syntax is that it's inconsistent with other literals, that it doesn't generalize to other literals (we can't do it for List because the List constructor takes an argument), and that it might interact weirdly with future features. If we allow spreads in collections, but not in arguments, it looks weird to allow it as Set(... e). It will probably interact well with var-args - we can make the Set constructor take an arbitrary number of arguments, then we just have to special-case the const version, which we already do with the Symbol constructor. All other classes can then also use the same syntax for non-const constructors.

The Set[e1, e2] syntax does not look like a constructor invocation, more like a marked literal, which is good.
It falls a little short on Set[e1], where we would again have to special case the syntax to not be an index lookup. It is not a feature we can generalize to other classes since it's specific syntax, unless we define function[e1, e2] as syntactic shorthand for function([e1, e2]) when function has a function type. That's more overloading on e1[e2], though.

So, the tradeoffs I see are:

Syntax consistent generalizes avoids ambiguity
{e1, e2} yes no no
Set(e1, e2) no no yes (with no varargs)
Set(e1, e2) yes yes yes (with varargs)
Set[e1, e2] no yes no

By "consistent" I mean that the syntax is similar to other similar constructs in the language. The {e1, e2} syntax is similar to list and map literals. The Set(...) syntax is similar to other vararg functions if we have vararg, otherwise it's not (it will still be special cased for const invocations, that part does not generalize). The Set[...] syntax is unlike anything else.

With "generalizes" I mean that the syntax can be made consistent by generalizing it to other similar problem areas. That is, can the same syntax be used to solve other problems.
The {...} syntax does not generalize at all (but it is already consistent, so that's not so bad). The Set(...) syntax generalizes if we have varargs elsewhere, otherwise it doesn't. The Set[...] syntax could probably be made to generalize to other collections, but it's hard to specify exactly which things are allowed.

The "avoids ambiguity" means that the syntax is not ambiguous, it doesn't introduce grammatically similar syntax meaning different things in a way that has to be disambiguated explicitly in the specification. The {...} syntax has the ambiguity problem with {}. It's only a corner case, but it still takes up a significant part of the proposal. The Set(...) syntax is arguably unambiguous without varargs, and completely unambiguous with (it means exactly what its looks like then). The Set[...] syntax has the id[e1] conflict with index lookup that has to be handled depending on the meaning of id.

Some points in here can probable be argued about (the precise answer would perhaps be "yes(ish)" or "mostly" in some cases), but if we are planning varargs, then we should probably consider the Set(...) notation as a good match.

@munificent
Copy link
Member

The Set[e1, e2] syntax does not look like a constructor invocation, more like a marked literal, which is good.

That looks an awful lot like matrix[x, y] to me. The fact that we don't currently support multi-argument index operators is an arbitrary limitation and one that's always felt annoying to me. I do fairly often have code that works with multi-dimensional array-like objects.

One idea I've toyed with is syntax like this:

var numbers = Set.[1, 2, 3];
db.insert.{
  name: "jane",
  id: 1234
};

So . followed by a collection literal is a special way of passing that literal to the invoked thing. Maybe it just becomes a positional argument? I haven't taken the time to try to figure out what the semantics really should be though. It might be nice to not have to reify the collection before passing it to the invoked thing.

C#'s collection initializer syntax instead translates similar syntax to a series of mutating calls on the object. That has its pros and cons. It avoids the duplicate allocation, but it forces the underlying object to be mutable.

@pulyaevskiy
Copy link

pulyaevskiy commented Oct 19, 2018

How about introducing a prefix for collection literal, similarly to how we currently do with raw strings (r"abc")? For instance:

final foo = s[23, 45]; // s as in Set
final bar = @[23, 45]; // just a non-letter symbol
final bar = <int>@[23, 45]; // with types 

@munificent
Copy link
Member

final foo = s[23, 45]; // s as in Set

This would be ambiguous if you used it to create a single-element set:

final foo = s[23];

At that point, it clashes with the existing syntax to call the index operator on s.

final bar = @[23, 45]; // just a non-letter symbol

This would work, but I think it would be confusing for users since @ is associated with metadata annotations in Dart.

@matanlurey
Copy link
Contributor

Silly question: Is there a compelling reason to have Set-literal instead of just a Set with variadic arguments? It sounds like it would be non-breaking to eventually support some sort of literal at a later point in time, and I imagine most (all?) people would be fine with Set(1, 2, 3).

@munificent
Copy link
Member

See #53, which proposes exactly that. The main limitation is that we don't actually have variadic arguments in the language.

@natebosch
Copy link
Member

See also previous discussion at dart-lang/sdk#3792

@mit-mit
Copy link
Member

mit-mit commented Feb 27, 2019

Closing as set literal shipped in Dart 2.2

@mit-mit mit-mit closed this as completed Feb 27, 2019
@eernstg eernstg added the enhanced-const Requests or proposals about enhanced constant expressions label Dec 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhanced-const Requests or proposals about enhanced constant expressions request Requests to resolve a particular developer problem
Projects
None yet
Development

No branches or pull requests

10 participants