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

Inline value declarations: where / let...in... #54292

Closed
lukehutch opened this issue Dec 9, 2023 · 3 comments
Closed

Inline value declarations: where / let...in... #54292

lukehutch opened this issue Dec 9, 2023 · 3 comments

Comments

@lukehutch
Copy link

lukehutch commented Dec 9, 2023

Many languages have the ability to declare named values inline, in an expression context. In Haskell, you have two options -- where and let...in...:

https://wiki.haskell.org/Let_vs._Where

(Sorry, I know there's an issue about this somewhere, but I looked hard and couldn't find it.)

Dart really needs this, because in Flutter, you end up building these massive expression trees when building a UI. If you need a named value in the middle of the tree, you have to precompute it in a separate statement before the expression tree that you're building.

If, however, in your expression tree, you're using an inline list-builder for statement, then you have no way to declare the value derived from the thing you're iterating over, before using it.

This is particularly problematic when calculating the value is very computationally expensive, and you need to use it twice.

For example, I am building a UI that calculates a number (which is a costly operation), for each item in a list, and then if and only if the value is greater than zero, it adds the number in a bubble to the end of the item's derived UI.

Right now I have to do something like the following:

Iterable<T> conditionalYield<T, V>({
  required V Function() calcValue,
  required bool Function(V value) ifTrue,
  required T Function(V value) thenYield,
}) sync* {
  final value = calcValue();
  if (ifTrue(value)) {
    yield thenYield(value);
  }
}

then I can use it like this:

Column(
  children: [
    for (final item in items)
      Row(
        children: [
          ItemLabelWidget(item),
          ...conditionalYield(
            calcValue: () => item.calcExpensiveValue(),
            ifTrue: (value) => value > 0,
            thenYield: (value) => ValueBubble(value),
          ),
        ],
      ),
  ],
),

But I wish I could do something like

Column(
  children: [
    for (final item in items)
      Row(
        children: [
          ItemLabelWidget(item),
          let value = item.calcExpensiveValue()
            in if (value > 0)
              ValueBubble(value),
        ],
      ),
  ],
),

(Yes, I know build is not supposed to be doing a lot of computational work, but the general principle here is "Don't Repeat Yourself" (DRY), and let...in... is useful in many other situations and for many other reasons too..)

I should add that I far prefer let...in... over where, because where is not as readable (values are used before they appear in the source).

@julemand101
Copy link
Contributor

I think the last example can today be written as:

Column(
  children: [
    for (final item in items)
      Row(
        children: [
          ItemLabelWidget(item),
          if (item.calcExpensiveValue() case final value when value > 0)
            ValueBubble(value),
        ],
      ),
  ],
),

Another discussion would then be if that solution is "pretty" or close enough to the suggested feature.

@lrhn
Copy link
Member

lrhn commented Dec 9, 2023

You could also use

  for (var value in [item.expensiveComputation()]) ...

as a let before patterns, but using if-case is definitely prettier than that.

There is a number of proposals for local bindings, they're all in the language repository because it is a language change, fx dart-lang/language#2703 and dart-lang/language#1420 (my personal favorite).

@lukehutch
Copy link
Author

          if (item.calcExpensiveValue() case final value when value > 0)

Thanks, I didn't even know that syntax existed!

  for (var value in [item.expensiveComputation()]) ...

This is very clever, if probably a little inefficient...

There is a number of proposals for local bindings, they're all in the language repository because it is a language change, fx dart-lang/language#2703 and dart-lang/language#1420 (my personal favorite).

No wonder I couldn't find an issue on this, I was looking in the wrong project :-) the "lang" in "dart-lang/sdk" always seems to trick my brain into thinking this is also the project for language issues.

I'll go ahead and close this then. Thanks for the interesting suggestions of syntax that works today.

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

No branches or pull requests

3 participants