Skip to content
This repository has been archived by the owner on Oct 8, 2024. It is now read-only.

Combine nested iterators (a sort of flatMap) #45

Closed
caub opened this issue Aug 24, 2019 · 11 comments
Closed

Combine nested iterators (a sort of flatMap) #45

caub opened this issue Aug 24, 2019 · 11 comments

Comments

@caub
Copy link

caub commented Aug 24, 2019

Those 2 expressions give the same result, but only the first one can iterate lazily

[...tripleSorted(1, 10)];

[...range(1, 10)]
  .flatMap(a =>   [...range(a+1, 10)]
    .flatMap(b => [...range(b+1, 10)]
      .flatMap(c => [[a, b, c]])
    )
  );

// where range and tripleSorted are:
function* range(si, ei) {
  for (let i = si; i < ei; i += 1) yield i;
}

function* tripleSorted(start, end) {
  for (const a of range(1, 10)) {
    for (const b of range(a+1, 10)) {
      for (const c of range(b+1, 10)) {
        yield [a, b, c];
      }
    }
  }
}

Would it possible, with this proposal, or with an additional iterator method, to combine those 3 range() iterators lazily and behave like tripleSorted?

Edit: some code achieving the same result lazily

function range(si, ei) {
  return ({
    *[Symbol.iterator]() {
      for (let i = si; i < ei; i += 1) yield i;
    },
    *flatMap(fn) {
      for (const o of this) yield* fn(o);
    },
    *map(fn) {
      for (const o of this) yield fn(o);
    }
  })
}

const tripleIt = range(1,10)
  .flatMap(a => range(a+1,10)
    .flatMap(b => range(b+1,10)
      .map(c => [a, b, c])));
[...tripleIt]

the idea is to be able to express something equivalent to python's list comprehensions (even if it's not necessarily lazy in that case):

 [(a,b,c) for a in range(1,10)
          for b in range(a+1,10)
          for c in range(b+1,10)]
@Alexendoo
Copy link
Contributor

This is essentially a Cartesian product. If the proposal includes a .flatMap I think you'd be able to remove the spreads to get an iterator version of the same thing, e.g.

range(1, 10)
  .flatMap(a => range(a+1, 10)
    .flatMap(b => range(b+1, 10)
      .map(c => [a, b, c])
    )
  );

@devsnek
Copy link
Member

devsnek commented Aug 25, 2019

Now that the proposal has been simplified I wouldn't be totally against adding a flatMap /cc @domenic

@bakkot
Copy link
Collaborator

bakkot commented Aug 25, 2019

@michaelficarra expressed interest in flatMap in #13.

@devsnek
Copy link
Member

devsnek commented Aug 25, 2019

Does this look good?

      <emu-clause id="">
        <h1>%Iterator.prototype%.flatMap ( _mapper_ )</h1>
        <p>%Iterator.prototype%.flatMap is a built-in generator function which, when called, performs the following steps:</p>
        <emu-alg>
          1. Let _iterated_ be ? GetIteratorDirect(*this* value).
          1. If IsCallable(_mapper_) is *false*, throw a *TypeError* exception.
        </emu-alg>
        <p>The body of %Iterator.prototype%.flatMap is composed of the following steps:</p>
        <emu-alg>
          1. Repeat,
            1. Let _next_ be ? IteratorNext(_iterated_).
            1. If _next_ is *false*, return *undefined*.
            1. Let _value_ be ? IteratorValue(_next_).
            1. Let _mapped_ be ? Call(_mapper_, *undefined*, &laquo; _value_ &raquo;).
            1. Let _innerIterator_ be ? GetIteratorDirect(_mapped_).
            1. Let _innerAlive_ be *true*.
            1. Repeat, while _innerAlive_ is *true*,
              1. Let _innerNext_ be ? IteratorNext(_innerIterator_).
              1. If next is *false*, set _innerAlive_ to *false*.
              1. Else,
                1. Let _innerValue_ be ? IteratorNext(_innerNext_).
                1. Perform ? Yield(_innerValue_).
        </emu-alg>
      </emu-clause>

@bakkot
Copy link
Collaborator

bakkot commented Aug 27, 2019

@devsnek To match Array.prototype.flatMap, you may want to allow non-iterators to be returned by the mapping function (which will act as if it had returned an iterator over that single value).

@zloirock
Copy link
Contributor

What should it flatten? Only iterators? What about iterables? Related tc39/proposal-collection-methods#34

@devsnek
Copy link
Member

devsnek commented Aug 27, 2019

It should probably flatten iterables, mapping over random values should use the outermost protocol.

@bakkot
Copy link
Collaborator

bakkot commented Aug 27, 2019

The answer to that depends somewhat on whether there might ever be a .flat. if so, you'd want to match that. But there flattening strings to characters is probably undesirable.

@michaelficarra
Copy link
Member

Only iterators. "X.prototype.flat should only flatten Xs" was the guiding principle behind the design of Array.prototype.flat and Array.prototype.flatMap.

@zloirock
Copy link
Contributor

@michaelficarra Set#flatMap should flatten only Set? So, we wanna somehow handle each element of Set and return some values - we should wrap them to Set instead of returning those values in the array literal? Doubtful way. The same for iterators.

@devsnek
Copy link
Member

devsnek commented Aug 27, 2019

I'd prefer to use Symbol.iterator as the indicator, which should be fine as this proposal also encourages all iterators to be iterable as well.

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

No branches or pull requests

6 participants