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 variadic Set constructor #53

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

Dart variadic Set constructor #53

lrhn opened this issue Oct 17, 2018 · 8 comments
Assignees
Labels
feature Proposed language feature that solves one or more problems

Comments

@lrhn
Copy link
Member

lrhn commented Oct 17, 2018

We have decided to not use this idea.

Proposal for #36

I'll write the design in-line here. It's short enough that a separate document shouldn't be needed.

Proposal

The current factory Set constructor declared in dart:core has no parameters.
When called as Set() it creates an empty linked hash-set.
It cannot be called as const since it is not a const constructor.

The specification will add the following clause for non-const constructor invocations:

If the invoked constructor is the Set constructor of the Set class of the platform libraries, the type arguments are omitted or contain exactly one type argument T, and the arguments list contains a number of positional arguments and no named arguments, then this invocation is not a compile-time error, but instead denotes a set literal.

If the type argument is omitted, it will be inferred by type inference.
Type inference treats a set literal similarly to a list literal, the type argument of the set, T, may be inferred
from the static context type, or it may be inferred from the static type of the element expressions.
The static context type of each positional argument is T.
The static type of the set literal is the exact type LinkedHashSet<T>
It is a compile-time error if any of the argument expressions have a static type that is not assignable to T.

The set literal is evaluated by first evaluating each of the positional arguments to a value,
and then the set literal evaluates to an object of type LinkedHashSet<T> containing the
argument values, with an iteration order corresponding to the argument expression's source
position.
If two argument values are equal, only the latter of them is included in the set.

For const constructor invocations, we add:

If the invoked constructor is the Set constructor of the Set class of the platform libraries, the type arguments are omitted or contain exactly one type argument T, and the arguments list contains a number of positional arguments and no named arguments, then this invocation is not a compile-time error, but instead denotes a constant set literal.

The static typing of a constant set literal is the same as for a non-constant set literal,
except that the static type is exactly Set<T>, not LinkedHashSet<T>.

It is a compile-time error if any argument expression, ei, is not a compile-time
constant. Let ci be the constant value that ei evaluates to.
It is a compile-time error if any ci has an implementation of the == operator
different from the one inherited from Object.

The constant set literal is evaluates to an object of type Set<T> containing the
constant argument values, with an iteration order corresponding to the argument expression's source
position.
If two argument values are equal, only the latter of them is included in the set.

Constant set literals are canonicalized. If a constant set literal has already been evaluated which
contains the same elements in the same order, then this set literal instead evaluates to the first
such value that was created.

Future Compatibility

This design assumes no feature other than the ones already in the language.
If Dart received "varargs", then we could define Set as a variadic constructor and handle the non-constant case in normal code. The constant case still needs special handling since that constructor cannot be constant. We will likely define the constructor as external const factory Set([... elements]); which means that the platform can cheat without violating any syntactic rules.

@lrhn lrhn added the feature Proposed language feature that solves one or more problems label Oct 17, 2018
@lrhn lrhn self-assigned this Oct 17, 2018
@munificent
Copy link
Member

I really hate giving the core libraries special magic powers. Every time we do that, we end up frustrating users who have similar legitimate use cases. (See: patch files and config-specific imports, overloaded return types on arithmetic operators, the hand-picked set of APIs allowed in const expressions, sealing the primitive types like num, etc.)

I don't think we should do this unless we also add user-defined varargs. If we do that, then this seems reasonable to me.

@lrhn
Copy link
Member Author

lrhn commented Oct 22, 2018

I agree with Bob. If we will never add varargs, then we should just use {e1, ..., en} syntax.
If we expect to add varags "soonish", then I would prefer Set(e1, ..., en) because of the syntactic simplicity.
If we think we might add varargs evantually, but have no current concrete plans, then it's going to be a judgement call. Obviously, this is the case we are in :)

We do already give core library special magic powers when it comes to constants and to literals.

The only classes that have literals (aka. language syntax support) are List, Map, Symbol, Type, int, double, bool, String, and Null (and, arguably, function types). Those are core types. Any other class needs to go through a constructor to create an instance.

Anything non-trivial that you can do in a const expression is an operation of a core library class.

Adding a constant set literal puts us squarely in that category of things, so it would not be unprecedented to add magic.
The difference between this proposal and the {} proposal is that we don't add visibly magical syntax. It's still magical, it just doesn't look that way. If we have varags, we will move part of the feature from being magic to being sufficiently advanced technology that is available to everyone. We will need to keep a little magic to make constants work.

@Hixie
Copy link

Hixie commented Oct 22, 2018

This is the only compelling argument I've heard for varargs. It's pretty compelling though. Set(...) is definitely better than {,} or the other tricks we've come up with to make {} unambiguous. (Especially when you consider the spread operator which makes it even more ambiguous.)

@lrhn
Copy link
Member Author

lrhn commented Oct 23, 2018

The spread operator is actually a problem with the Set(...) notation if we don't allow spreading in parameter lists, because the we won't be able to spread into set literals the same way we do in list literals, at least not without further magic.

If we do get varargs/rest arguments, we should probably be able to spread into, at least, those parameters, so we would allow Set<int>(... intList).

@munificent
Copy link
Member

munificent commented Oct 24, 2018

Adding a constant set literal puts us squarely in that category of things, so it would not be unprecedented to add magic.

This is true, but I believe we should aim to reduce the amount of magic over time, not double down on it. My experience has been that anything so useful that we language/core lib folks give ourselves the magic power to use is also useful for other people too.

if we don't allow spreading in parameter lists, because the we won't be able to spread into set literals the same way we do in list literals, at least not without further magic.

Yes, I don't think it makes sense to support rest parameters without a spread operator. Otherwise, users will find themselves in this situation:

// Code you don't control.
expectsRest(List ...things) { /* code */ }

// Also code you don't control.
List returnsList() { /* code */ }

main() {
  var stuff = returnsList();
  expectsRest(/* want to pass stuff here... */); // Aww dang. :(
}

Every language I know that has rest parameters or varargs has a mechanism for passing a collection of objects as a set of rest parameters. The only exception is Swift, and it's an open issue there.

The rest params proposal I wrote includes a spread syntax for arguments for this reason.

@Hixie
Copy link

Hixie commented Oct 24, 2018

I wouldn't view this as adding magic. I'd view this like FutureOr: a temporary step towards an eventual language feature. We will eventually have union types, at which point we just write:

  typedef FutureOr<T> = Future<T> | T;

Similarly we will eventually have varargs, at which point we reimplement Set(...) as varargs.

Magic is something we can't explain. We can explain both of these, just not in terms of something that exists yet.

@munificent
Copy link
Member

Similarly we will eventually have varargs, at which point we reimplement Set(...) as varargs.

If we decide to do varargs. But I'm really hesitant to effectively commit ourselves to needing a feature without committing the time to do the feature.

FutureOr is a good example. It's almost two years old now and we still don't have real union types in the language or immediate plans to add them.

We can explain both of these, just not in terms of something that exists yet.

True, but we can't implement them in terms of something that doesn't exist yet. If we add a variadic constructor for Set, then:

  1. The type system needs to understand the type of that constructor.
  2. The code for static checking an invocation needs to handle a variadic invocation.
  3. When you pass the wrong types to it, the error message needs to know how to represent an error in a variadic call.
  4. Type inference needs to handle inferring from varargs.
  5. Dartdoc needs to be able to display the signature of Set().
  6. The runtime needs to have a calling convention for invoking Set's constructor. (And the patch file mechanism might need to handle it too.)
  7. Auto-complete and other IDE support needs to not get confused when inside the argument list of Set().
  8. The analyzer AST API used by all the codegen tools needs to expose a representation of a variadic invocation.
  9. To the degree that we still support mirrors, mirrors needs to do something with it.

Etc.

At that point, we may as well just do rest params.

@Hixie
Copy link

Hixie commented Oct 26, 2018

Two years is nothing on the grand scheme of things. You have to think of language and API development on the time scale of decades.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

3 participants