dart-lang / language

Design of the Dart language
Other
2.67k stars 205 forks source link

Should tuples be equivalent to argument lists? #1293

Open lrhn opened 4 years ago

lrhn commented 4 years ago

The current proposal for tuples allow named entries and positional entries, and can in many ways be seen as an abstraction over argument lists. It's not complete, because it doesn't cover the empty tuple, but it does have a singleton tuple.

If we allow tuples to work like argument lists, then every function can now be seen as an unary function from a tuple type to its result. (We might not allow f tupleVariable to call the function, but that's mainly for syntactic reasons, not because it doesn't make sense, and we can probably allow f(...tupleVariable)).

If argument lists are tuples, then tuple types should be parameter lists. The syntax is already close, a tuple type can be used as the parameter types of a Function type, e.g., (int, int, {int x}) can be used as void Function(int, int, {int x}). It's not a complete match though:

Still, if we ignore optional parameters for now, we can see tuple types as the shape of a parameter list. We could then allow spreading a tuple type into a parameter list:

typedef F<R, P extends Record> = R Function(... P);

That could allow some abstraction over the "width" of a tuple. It would probably be too complicated, but it could (highly speculatively) allow for something like

R invoke<R, P extends Record>(T Function(... P) f, P args) => f(...args);

That's probably too ambitious (would make more sense for a language where functions were always unary and tuples were built in from the start, like ML).

lrhn commented 4 years ago

If we go for "everything like in function definition", probably more like everything in a function type definition, then we have optional parameters (both optional positional and optional named, but not at the same time).

I'm not sure it's the right design for tuple types. If you do (int, int, [int]) p = tuple;, I'd expect you to only be able to use the first two entries, but for functions, you get the third entry as well, with a default value. Or alternatively, the assignment is not valid because the third entry needs a default value, so it'd be (int, int, [int = 0]) p = tuple;.

It's not a slam-dunk, but I guess it can work.

munificent commented 4 years ago

That's probably too ambitious (would make more sense for a language where functions were always unary and tuples were built in from the start, like ML).

Yeah, I think we will eventually hit a wall if we try to push too far in this direction. Dart isn't ML and Dart-y tuples will probably not be 100% like ML tuples.

I look at records in Dart more pragmatically: it's an immutable collection that the type system can see into. Nothing more, nothing less. Spreading a record into an argument list is a natural extension of that, but I don't think we need to reach 100% parity between tuple types and parameter lists for that to work.

lrhn commented 3 years ago

If we don't need argument-list parity, then I think we can easily go in two extreme directions:

I think either approach can work.

The "named only" approach might feel overkill if something is really just an unstructured pair, but ... it never is. If a pair occurs as an inherent part of a class or method API (not just a type argument), then each position has a meaning. A MapEntry is not just a pair, it's a key/value pair. A point is not just a pair, it's x/y coordinates. There is always an underlying meaning, and therefore a name, it's just that the name sometimes so obvious that we want to be able to not have to write it. That isn't necessarily a good choice, though (favor explicit over implicit!).

The "positional only" approach is a move towards a physical representation of the data. Semantics must be supplied from the outside. If you want to assign names to the parts, it's your job, not part of the tuple. (Maybe we can have "static names", so a tuple can be defined as (int x, int y) point = ...; print(point.x + point.y);, where the x is part of the point declaration, not the underlying run-time tuple, which is just (int, int).)

Mixing the two muddies the waters. Which one is "the right one" depends on which problem tuples/records are intended to solve.

mateusfccp commented 3 years ago

As I am biased to ML I would love to see a complete equivalence. I know that there are some limitations to it in the context of Dart, tho.