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

Destructuring #207

Open
lrhn opened this issue Feb 5, 2019 · 66 comments
Open

Destructuring #207

lrhn opened this issue Feb 5, 2019 · 66 comments

Comments

@lrhn
Copy link
Member

@lrhn lrhn commented Feb 5, 2019

Dart could have some way to easily "destructure" composite values.

Example proposal from the DEP process: https://github.com/DirectMyFile/dep-destructuring (by @kaendfinger).

@andreashaese
Copy link

@andreashaese andreashaese commented Feb 6, 2019

For more inspiration, I'll add some Swift examples.

Tuples & tuple desctructuring

let tuple = (1, "A")
let (x, y) = tuple

Tuple dot notation

let triple = (1, 2, 3)
let (a, b) = (triple.0, triple.2) // a = 1, b = 3

Binding with pattern matching

switch tuple {
case let (x, y) where x > 0 && y == "A":
    print(x, y)
    
case let z:
    print(z)
}

Binding with for loops

let dictionary = [1: "one", 2: "two", 3: "three"]

for (key, value) in dictionary {
    print(key, value)
}

Wildcards

for (_, value) in dictionary {
    print(value)
}

switch triple {
case let (a, _, _):
    print(a)
}

@vanesyan
Copy link

@vanesyan vanesyan commented Feb 10, 2019

BTW it will allow a sweet feature like swapping data without necessity of temporal variable, e.g.:

Currently:

int a = 1;
int b = 2;

int tmp = a;
a = b;
b = tmp;

with destructuring:

int a = 1;
int b = 2;

[a, b] = [b, a];

a; // 2
b; // 1

@andreashaese
Copy link

@andreashaese andreashaese commented Feb 10, 2019

@thomasio101
Copy link

@thomasio101 thomasio101 commented Feb 16, 2019

The proposal only details destructuring in the context of assignment, such as in variable assignments/declarations, or in loops. But I'm seeing quite a potential here for using destructuring in the context of function/constructor/accessor arguments, as is the case in Python.

Examples

These examples have all been written in Python.

Iterable unpacking

def my_function(x, y):
  print(x)
  print(y)

my_tuple = (1, 2)

my_function(*my_tuple)

This prints the following;

1
2

Mapping unpacking

def my_function(x, y):
  print(x)
  print(y)

my_dictionary = {'x':1, 'y':2}

my_function(**my_dictionary)

This prints the following;

1
2

It also works in reverse;

my_dictionary = {'y':2, 'x':1}

my_function (**my_dictionary)

This will print the following

1
2

NOTE See the Python specifications for a more comprehensive look at the "unpacking" functionality

Issue

The problem that this concept poses, is the proposal's lack of an operator like the * or ** operators from Python. This lack of operators means there's no distinguishment between destructuring a value to parameters and passing the value itself as an argument.

@tatumizer
Copy link

@tatumizer tatumizer commented Feb 18, 2019

.. several posts irrelevant to the issue of destructuring - deleted by the author

1 similar comment
@thomasio101
Copy link

@thomasio101 thomasio101 commented Feb 19, 2019

.. several posts irrelevant to the issue of destructuring - deleted by the author

@thomasio101
Copy link

@thomasio101 thomasio101 commented Feb 19, 2019

@thomasio101
Copy link

@thomasio101 thomasio101 commented Feb 19, 2019

@tatumizer I've already published a test on the repo which eliminates the possibility of a significant performance differential between positional, named and mixed arguments in the verbose method.

I will also be doing other performance tests in the repository, but maybe it would be interesting if you open an issue on there with your test's source code? That way I can also provide some feedback and possibly replicate the test my self to compare the results.

EDIT

  • In case you read over the link to source code in my report. Here it is.
  • I've also made some code to generate Markdown tables, you can find it here - I guess it might come in useful for visualizing your own results.

@thomasio101
Copy link

@thomasio101 thomasio101 commented Feb 19, 2019

I was aware of that, my report's hypothesis states that I did not expect any significant difference between named and positional arguments in verbose code.

The reason I tested it was just to make sure we weren't going off a wild guess.

@thomasio101
Copy link

@thomasio101 thomasio101 commented Feb 19, 2019

That sounds like a good idea, anyways, I'm going to make a generic API for running tests, although I need to see if the overhead is not to much (maybe I could over-engineer this a bit with a web panel with fancy graphs and the ability to monitor tests remotely :)......)

@tatumizer
Copy link

@tatumizer tatumizer commented Feb 19, 2019

I opened an issue in your repo to move further discussion from this thread. Let's communicate via this issue, I will be happy to help you to build a framework
Let's delete our messages here, OK? (Only you can delete your posts)

@tatumizer
Copy link

@tatumizer tatumizer commented Feb 19, 2019

@lrhn: Property destructuring, as defined by this proposal, is ambiguous. List itself is an object having a number of properties, so in the expression var [first, length] = [ 0, 1 ]; we have 2 candidates for "length" assignment: [0, 1][1] (which is 1) or [0, 1].length (which is 2)

@tatumizer
Copy link

@tatumizer tatumizer commented Mar 7, 2019

Dart has a syntax problem WRT destructuring. If we allow this:
var [String a, int b] = ["Hello World", 5];
then, for consistency, var String a = "blah" has to be allowed, too.
Further, what about inferred types?
var [a, b] = ["Hello World", 5];
Is it a valid declaration? Probably it is, but then
var [String a, b] = ["Hello World", 5];
Is "b" here also a String, or dynamic, as suggested by "var"? Or inferred as "int"?
And what about this:
int [a, b] = [0, 2];
Is it a valid declaration?

@lrhn
Copy link
Member Author

@lrhn lrhn commented Mar 8, 2019

Lists are not tuples, so I would't use var [a, b] = ["Hello world", 5]; for tuples. The expression on the right-hand side is an existing Dart expression with type List<Object>.
Doing list deconstruction this way is possible (and risks throwing if the list expression is not a literal and ends up not having two elements).

For a tuple-based syntax like var (a, b) = ("Hello world", 5); I'd expect the RHS to have type String * int and infer String for a and int for b.

@tatumizer
Copy link

@tatumizer tatumizer commented Mar 8, 2019

Probably, destructuring for lists doesn't make much practical sense anyway.
Suppose we have tuples

print(("hello", 5).runtimeType); // what does it print? "String*int"?
String*int (a,b); // is it a valid declaration? 
var (a,b); // is it a valid declaration? is it "dynamic*dynamic" ?

I think all answers are "yes", juts wanted to double-check

@lrhn
Copy link
Member Author

@lrhn lrhn commented Mar 8, 2019

I won't put my money on any specific tuple type syntax yet, too many options are open. I intended String * int as the semantic type here.

The point of tuples, maybe even named tuples like (1, "yes", reserve: "table") is that a tuple has a static type, so we can spread it in a parameter list and know statically which slots it will apply to, and whether it matches the required parameter count and types.

You can spread a list in a list, or a map in a map. You can't just spread a map or list in a parameter list because it's a different structure with different requirements (like a per-element type), but a tuple can be an abstraction with the same structure as a parameter list, so it would make sense to spread a tuple in another tuple, or in a parameter list.

(Maybe (String a, int b) p = someTupleOperation(); print(p == (a, b)); // true).

@tatumizer
Copy link

@tatumizer tatumizer commented Mar 8, 2019

Tuples are good for functional programming. I played with some variants the other day, came up with notation #(a, b) to make it shout louder, b/c raw parentheses hurt readability IMO. Not sure.
Anyway, tuples make it possible to turn Iterable.fold into universal looping mechanism, e.g.

Iterable<int> codePoints(String string) {
  processNextChar(#(List accum, int prev), int char) { // parameter - tuple, with automatic destructuring
    if (prev != null && isLeadSurrogate(prev) && char != null && isTailSurrogate(char)) {
      return #(accum..add(combineSurrogates(prev, char)), null); // returning tuple, denoted as #(value, value)
    } else {
      return #(prev == null ? accum : accum..add(prev), char); // returning tuple
    }     
  }

  return string.codeUnits.followedBy(<int>[null]).fold(#([], null), processNextChar)[0]; // passing initial tuple in "fold"
}

Still, I like the version with sync* more - the example just illustrates the point about fold. It's probably a standard pattern in functional programming, I assume.

@gamebox
Copy link

@gamebox gamebox commented Mar 12, 2019

Tuples with () syntactical representation appear in a number of languages where parens are also used for declaring parameter lists. See Scala, Rust, and Swift to name a few. I used them in Scala for years and found no readability problem with them(data point of 1, I know). Now, that form can present some interesting challenges in function declaration syntax of the sort Dart has(C style), but it should definitely be manageable. I will also say being able to spread a tuple into a parameter list is intriguing as an idea, but I think it could be confusing to users. There is just a lot of edge cases where the behavior would be hard to intuit. I also don't know if I've ever encountered a languages with a partially named Tuple before, usually Tuples and Structs/Records are very distinct(even if the literal is similar syntactically).

@tatumizer
Copy link

@tatumizer tatumizer commented Mar 12, 2019

@gamebox : without "#" as a prefix for tuple, as in #(1, "Hello"), how do you write 1-tuple? 0-tuple?

@lrhn
Copy link
Member Author

@lrhn lrhn commented Mar 13, 2019

You write a one-tuple as (1). Through no coincidence, a one-tuple is a value.
A zero-tuple is harder, but () is an option. The next question is whether you ever need a zero-tuple, it's a singleton type with no useful behavior, so you might as well use null.
(This assumes that there is no type relation between tuples with different structures, so a zero-tuple type is never a sub- or super-type of a one tuple type).

@andreashaese
Copy link

@andreashaese andreashaese commented Mar 13, 2019

In Swift, you're using the empty tuple basically everywhere without even noticing:

public typealias Void = ()

@lrhn
Copy link
Member Author

@lrhn lrhn commented Mar 13, 2019

If we had tuples, maybe we should just make null be an alias for the zero-tuple.

@tatumizer
Copy link

@tatumizer tatumizer commented Mar 13, 2019

The question whether expression () can be considered as a synonym of null is an interesting one.
In some languages, nil is just another name for (), but in Swift (I just checked it out), it doesn't seem to be the case (not sure).

In dart, we have an intriguing possibility to treat 0-tuple () as Nothing - which I believe, is a necessary concept. If we agree to model tuples by analogy with parameter lists of functions (and the idea of including named elements into the definition certainly points in this direction), then, quite naturally, function with no parameters corresponds to 0-tuple. On the other hand, when asked what we pass as a parameter to a function requiring no parameters, the only logical response we could give would be "Nothing". But the story doesn't end here.

Dart has some mysterious concept called "bottom type", which is not expressed in the language, but shows up in a language spec. Can it be that it's exactly *that" type? Which leads us to a bold
Conjecture: Nothing = () = bottom type

If we could agree on that, then we get an answer to a long-standing question about the type of ?null in null-elimination operator (see #219)

@lrhn
Copy link
Member Author

@lrhn lrhn commented Mar 13, 2019

It's unlikely that the zero-tuple type () will be bottom because the zero-tuple type contains one value (the empty tuple) and the bottom type is necessarily empty.

@tatumizer
Copy link

@tatumizer tatumizer commented Mar 13, 2019

It can't be! If we have foo() {...}, and we call it foo(), what is inside parentheses? I can't see anything there, so it must be Nothing, or completely non-existent thing, which is as good (or as bad) as "bottom".
"Nothing" is a tricky subject though, because the very act of mentioning it makes it a kind of "value" - probably, in the second-order logic. And it's exactly in that sense () is a value, but so is "bottom". No?

@lrhn
Copy link
Member Author

@lrhn lrhn commented Mar 13, 2019

No.

Ob-digression:

Tuples are product types. If you see types as sets of values (which is a slightly naive mode, but useful) then the tuple type (bool, bool) is the product of the set of booleans with itself (which is a set of pairs: {(x, y)|x in bool & y in bool}). The cardinality of bool * bool, say |bool * bool|, is the product of the cardinalities of the individual sets, |bool| * |bool| (aka. 2 * 2 = 4).

In general, then n tuple of a type, booln, would have cardinality |bool|n.
The one-tuple is the set itself, no surprise there.

From that it follows that the zero tuple, bool0, must have cardinality 1.

Up to isomorphism, all one-element sets are the same, and a one element set is is the neutral element wrt. set products (let unit be the singleton set {?}, then unit * bool is {(?, true), {?, false)} which is isomorphic with bool - inside this set the always-present "?" makes no difference, it's still a two-element set, and all two-element sets are isomorphic too).

The empty set is the neutral element wrt. set union, but an eliminator wrt. set product. It's similar to how 0 and 1 work with multiplication in the non-negative integers.

In this sense, the unit type and the empty type are very different, and bottom is the empty type, while the zero-tuple is the unit type.

@jkmpariab
Copy link

@jkmpariab jkmpariab commented Aug 13, 2019

another use case for this functionality is passing constructor arguments to routes when using onGenerateRoute without defining a redundant class to wrap arguments

@edwjusti
Copy link

@edwjusti edwjusti commented Oct 26, 2019

One common use for me coming from javascript is awaiting multiple futures and then destructuring the results by assigning them to variables like so:

const [user, purchases] = await Promise.all([
  fetchUser(),
  fetchPurchases()
]);

It would be handy to have something like that on dart for that specific use so that I don't block my async function by running my futures in parallel and easily extracting the result values to variables.

@raveesh-me
Copy link

@raveesh-me raveesh-me commented Nov 14, 2019

Faced the need for this right now!


  @override
  Widget build(BuildContext context) {
    return PageView(
      controller: _controller,
      dragStartBehavior: DragStartBehavior.down,
      children: <Widget>[
        Center(),
        ...currentMap.entries.map((mapEntry) => Viewpage()).toList();
      ],
    );
  }

would love to destructure the MapEntry in the parameter itself

@javiermrz
Copy link

@javiermrz javiermrz commented May 14, 2020

Are there any news related to this? Has it been considered as a possible addition?

@krevedkokun
Copy link

@krevedkokun krevedkokun commented May 14, 2020

Are there any news related to this? Has it been considered as a possible addition?

#546

@javiermrz
Copy link

@javiermrz javiermrz commented May 14, 2020

Are there any news related to this? Has it been considered as a possible addition?

#546

Thanks! Looks like no activity except for mentions since some months ago :(

@mateusfccp
Copy link
Contributor

@mateusfccp mateusfccp commented May 14, 2020

Thanks! Looks like no activity except for mentions since some months ago :(

The current focus of the development team is nnbd. Until then, I think they will not touch this kind of issue as it is not trivial.

@javiermrz
Copy link

@javiermrz javiermrz commented May 14, 2020

The current focus of the development team is nnbd. Until then, I think they will not touch this kind of issue as it is not trivial.

Nice, thanks for the quick update :)

@proninyaroslav

This comment was marked as off-topic.

@lukevenediger

This comment was marked as off-topic.

@proninyaroslav

This comment was marked as off-topic.

@mraleph
Copy link
Member

@mraleph mraleph commented Jun 1, 2020

I kindly ask people to take off-topic discussions somewhere else and keep stuff here strictly devote to the intricacies of language design.

As a general statement - there are a lot of language features to be implemented and there are only few of us. Contrary to what one could imagine language features require a lot of work from design to test coverage to implementation across all Dart platforms and tools - and sadly Dart team has only a finite number of people - hence we have to prioritize things very carefully. Sometimes the whole team has to be focused on a single feature - and only that feature is going to move forward. Right now it is NNBD. Once we are done with NNBD we will certainly go back to other slumbering features.

That said - this is not a discussion to be had on this particular issue.

@Levi-Lesches
Copy link

@Levi-Lesches Levi-Lesches commented Mar 8, 2021

Okay, which function is a duplicate of the other: this, or #68? Either way it seems if you fix one, you fix the other.

@tjx666
Copy link

@tjx666 tjx666 commented Apr 22, 2021

Very useful syntax!

@munificent
Copy link
Member

@munificent munificent commented Apr 24, 2021

Okay, which function is a duplicate of the other: this, or #68? Either way it seems if you fix one, you fix the other.

Neither are strictly duplicates. There are some languages (Lua, Go) that allow multiple returns without actually reifying them into a single destructurable object.

@mat100payette
Copy link

@mat100payette mat100payette commented Dec 16, 2021

What's the status on this ? Builtin tuples and destructuring has been a thing for like half a decade in C# and other languages.

@munificent
Copy link
Member

@munificent munificent commented Dec 17, 2021

We haven't had time to work on it recently. Destructuring is on my plate, and static metaprogramming has been keeping me busy. I am hoping to resume working on it fairly soon.

@mat100payette
Copy link

@mat100payette mat100payette commented Dec 17, 2021

We haven't had time to work on it recently. Destructuring is on my plate, and static metaprogramming has been keeping me busy. I am hoping to resume working on it fairly soon.

Lovely. Thanks for the quick update!

@PaulHalliday
Copy link

@PaulHalliday PaulHalliday commented Dec 30, 2021

Excited for this. Thanks for the hard work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Language funnel
Being discussed
Linked pull requests

Successfully merging a pull request may close this issue.

None yet