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

Multi asign, list deconstruction and better support for multiple returns #3822

Open
rkh opened this issue Mar 29, 2024 · 2 comments
Open

Multi asign, list deconstruction and better support for multiple returns #3822

rkh opened this issue Mar 29, 2024 · 2 comments
Assignees
Labels
enhancement New feature or request

Comments

@rkh
Copy link

rkh commented Mar 29, 2024

It would be nice to be able to easily accept more than one return value from a function.

This could be done by list deconstruction and/or map deconstruction.

Either of these would be great.

In the shortest form, I propose to implement support for the following:

$list: 1, 2;
$a, $b: $list...;

// alternative #1
$a, $b: $list;

// alternative #2
($a, $b): $list...;

// alternative #1 + #2
($a, $b): $list;

Possibly also with maps:

$map: (a: 1, b: 2);
$a, $b: $map...;

Motivation

I find myself returning more than one result from a function on a semi-regular basis, usually for private helper functions in libraries that process arguments a bit further. I would probably do so way more often if it wouldn't take multiple lines on the caller's end to deconstruct the result.

For me, this exclusively affects functions, as mixins can pass more than one argument to the content block.

List deconstruction

Current approach

@use "sass:list";

@function a() {
  @return 1, 2;
}

@function b() {
  $result: a();
  $a: list.nth($result, 1);
  $b: list.nth($result, 2);
  @return $a + $b;
}

Proposal

@function a() {
  @return 1, 2;
}

@function b() {
  $a, $b: a()...; // or ($a, $b): a();
  @return $a + $b;
}

Map deconstruction

Current approach

@use "sass:map";

@function a() {
  @return (a: 1, b: 2);
}

@function b() {
  $result: a();
  $a: map.get($result, a);
  $b: map.get($result, b);
  @return $a + $b;
}

Proposal

@function a() {
  @return (a: 1, b: 2);
}

@function b() {
  $a, $b: a()...; // or ($a, $b): a();
  @return $a + $b;
}

Reasoning: Parallels to arglists and @each

Deconstruction with a comma already works the same for arglists, which is where the inspiration comes from:

@function a() {
  @return 1, 2;
}

@function b($a, $b) {
  @return -b(a()...);
}

@function -b($a, $b) {
  @return $a + $b;
}

Note that in the above example, both list and map deconstruction are already supported (ie, both versions of a() from the sections above would work).

However, the proposed list deconstruction is also how @each already works:

$entries: 1 2 3, 4 5 6;

@each $a, $b, $c in $entries {
  @debug $a + $c;
}

Which means the examples from above could already be written as the following (to avoid using sass:list):

@function a() {
  @return (1, 2),;
}

@function b() {
  @each $a, $b in a() {
    @return $a + $b;
  }
}

Alternative: Closures

A fantastic alternative would be the ability to pass a closure / non-output-generating content block to a function, which would relate to #472 and #3213, assuming it could accept multiple arguments.

An example more closely resembling mixins:

@function a() {
  @return content(1, 2);
}

@function b() {
  @return a() using ($a, $b) {
    @return $a + $b;
  }
}

Contributing

I'm happy to work on a proposal, and could possibly even look into an implementation. Although I have never worked with Dart and I'm having trouble understanding the source code, if I'm honest, at least from a few cursory glances.

Off-topic: Looks like it has been a while since I last contributed to Sass.
@Goodwine
Copy link
Member

Goodwine commented Apr 1, 2024

IMO

  • Lists - no concerns
  • Maps - I imagine destructuring a map (foo: bar) would require the variable to be named $foo. But what if there was already a $foo variable or what if I wanted a different name? Would something like this work in a way that destructures a the key foo without shadowing or changing the hypothetically already-existing variable $foo?
    $foo as $foo-2 = (foo: bar) \

@Goodwine Goodwine added the enhancement New feature or request label Apr 15, 2024
@nex3
Copy link
Contributor

nex3 commented Apr 16, 2024

This is a well-reasoned proposal. Using the existing arglist behavior as the underlying semantics is clever, and helps keep the size of this feature relatively constrained. Although adding new features to the language does always come at a complexity cost, given how much overhead Sass's syntax gives the process of accessing collections I think this is likely to be a worthwhile place to put that complexity.

There are some important questions to settle before we move forward with this:

  • What syntax do we use? In addition to those outlined, it may make more sense to match JS in requiring square brackets when matching lists (as in [$a, $b]: 1, 2) since those are the only brackets that always indicate lists in Sass. Note that the $a, $b: 1, 2 syntax doesn't (obviously) support destructuring a single-element list.
  • What happens if the destructuring doesn't match in some way that would be an error in an arglist, as in [$a, $b]: 1, 2, 3? What if it doesn't match in ways that wouldn't be an error in an arglist, like [$a, $b]: 1 2?
  • Where can you destructure? Obviously variable assignments are the most obvious place, but what about within an arglist? Can I write @function foo([$a, $b])? What about @each [$a, $b]?
  • If we require brackets for destructuring, should we start requiring them in @each?
  • Do we have a built-in way to represent an unused matched value, like JS's const [a, , b] = ... or Dart's var [a, _, b] = ...? If so, what?
  • Do we allow default values in destructuring expressions like we do in arglists? If so, in what scope are they evaluated? Can they see prior destructured values?
  • Do we allow renaming variables when destructuring maps? If we do this and allow default values, how do we disambigutate the two?
  • Can rest arguments be destructuring expressions?
  • Do we allow destructuring expressions to contain rest arguments of their own?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants