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

Add list interspersion as built in function #49900

Open
noga-dev opened this issue Sep 2, 2022 · 3 comments
Open

Add list interspersion as built in function #49900

noga-dev opened this issue Sep 2, 2022 · 3 comments
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug

Comments

@noga-dev
Copy link

noga-dev commented Sep 2, 2022

I find myself needing to use interspersion a lot in flutter. For example in cases where I can't use ListView.separated I can just do children.intersperse(SizedBox(height: 10)); I think the community at large could benefit from having this feature built into the language. This would spare us from having to add it to every project.

@lrhn
Copy link
Member

lrhn commented Sep 5, 2022

Sounds like a possible extension on Iterable.

extension Intersperse<T> on Iterable<T> {
  Iterable<T> intersperse(T separator) sync* {
     var iterator = this.iterator;
     if (iterator.moveNext()) {
       yield iterator.current;
       while (iterator.moveNext()) {
         yield separator;
         yield iterator.current;
       }
     }
   }
}

It's not perfect. If we had lower-bounded type variables, the signature should be Iterable<S> intersperse<S super T>(S separator). We do not, so if you have a List<FooWidget> foos, you can't do foos.intersperse(SpearatorWidget()) to get a sequence of Widgets. You need to up-cast the original list before calling the extension, which is not a great affordance.

The alternative is a static/constructor, say on Iterable:

  factory Iterable.interspersed(Iterable<T> values, T separator) => () sync {
    // The above, with this↦values.
  }();

Not as easy to use: Itearble.intersperse(children, SizedBox(height: 10)). It does allow better typing, though.

@lrhn lrhn transferred this issue from dart-lang/language Sep 5, 2022
@lrhn lrhn added area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug labels Sep 5, 2022
@modulovalue
Copy link
Contributor

modulovalue commented Mar 10, 2023

I think that interspersion is extremely useful and that the standard library should provide a way to intersperse. (we already have a specialization of interspersion for strings called String.join)

There are many valuable modifications to the standard idea of interspersion that I've found to be useful:

  • Left/right interspersion allows one to provide separate elements that get prepended/appended to the resulting list only if the source list is not empty.

  • Expanding interspersion allows one to intersperse a sequence of values and not just a single value.

  • Strided interspersion allows one to intersperse with a stride and/or an offset.

  • Dependent interspersion could make the element have a lookbehind/lookahead on some elements in the source sequence.

  • Mapped interspersion would allow the to-be-interspersed element to have the same type as the values that the source sequence gets immediately mapped to.

For performance reasons it might make sense to intersperse in-place into a given list, and not to intersperse lazily on-demand on an Iterable.

Unfortunately, I don't see how such extensions can be supported with an API as simple as a single intersperse method. I also agree with Lasse that there would be important type inference related usability issues that could be frustrating and confusing.

Furthermore, the 'perfect' interspersion should use a custom Iterable because e.g. given a list, the length can be calculated without having to materialize the whole interspersed List/Iterable. (I'm pointing this out because implementing interspersion looks trivial on the surface, but doing it well is not)

In response to the flutter related interspersion issues, I think that Row/Column and others should have a custom separator parameter because needing to intersperse is common enough and this would give the framework more freedom to optimize for these use-cases (e.g. so that separators don't have to be redrawn).

It's complicated.

@modulovalue
Copy link
Contributor

Lasse wrote:

[...] so if you have a List foos, you can't do foos.intersperse(SpearatorWidget()) to get a sequence of Widgets. You need to up-cast the original list before calling the extension, which is not a great affordance.

Within the context of Flutter, in practice, the List value that users want to intersperse with some element will mostly come from List literals (I claim this to be true based on personal experience and see here for some evidence: flutter/flutter#16957, flutter/flutter#95521).

This special case has a safe solution, which is to provide an explicit type for the contents of the list:

void main() {
  // This would be inferred to Container, but Widget is what we want here.
  //           vvvvvvvv
  final list = <Widget>[
    Container(),
    Container(),
  ]
      .intersperse(const SizedBox())
      .toList();
}

(Perhaps this will be useful to someone that finds this issue)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

3 participants