tc39/proposal-function.sent

Generators: what are they good for?

Opened this issue · 5 comments

The rationale for a proposal should do two things:

  • illustrate what the feature is for and what it's not for
  • make the case that that's worthy of language-level support

The adder example doesn't really advance either of these. So let's talk a little about why we're doing this, and see if we can come up with examples that really motivate the feature.

(background: hi, my name is @jorendorff and my role in TC39 is to complain that the proposal README doesn't have good examples)

There's a value that a user can provide to a generator's iterator (and may assume will mean something) that the generator function can't possibly detect or receive - function.sent provides that value. I think the adder example, while contrived, shows how someone would intuitively use an iterator in a way that wouldn't be compatible with using a generator function to produce it.

Right. A less contrived example would be an improvement, but that's not the main issue.

I'm after evidence that sending values to generators has proven valuable enough to justify further investment.

My experience is that generators are fantastic for implementing iterators—they're like magic—but are otherwise brittle and inconvenient. Sometimes I want to use them as a general tool for inversion of control, but it doesn't work out. I usually (maybe always) end up needing a total rewrite, and usually very soon, before I even land a patch. Maybe it's just me; hence the question: Is the rest of the JS world doing a bunch of work with generators that this feature would facilitate? Either answer would help; certainly the proposal could be viable either way.

I guess with iterators I really feel like we're on solid ground, if only because I use them myself all the time (not really joking, unfortunately), so the Iterator methods proposal is an easy +1.

With this, it feels like we must be talking about supporting some other way of programming that I've never seen, and have actually tried and failed to make it work out. So what is that exactly? Let's put a little more of that in the README.

Here some practical example if it may help: Writable streams.

(written in Typescript for types)

import { promises as $fs } from 'fs';

async function * writeStream(path: string): AsyncGenerator<number, void, Uint8Array| null> {
  const fd: $fs.FileHandle = await $fs.open(path, 'w', 0o777);
  let position: number = 0;
  try {
    while (true) {
      const buffer: Uint8Array | null = yield position;
      if (buffer === null) {
        return;
      } else {
        if (buffer.length > 0) {
          const { bytesWritten } = await fd.write(buffer, 0, buffer.length, position);
          position += bytesWritten;
        }
      }
    }
  } finally {
    await fd.close();
  }
}

const stream = writeStream('file.txt');
stream.next(); // sadly we can't pass immediately a value (=> where this spec aims to find a workaround)
stream.next(new Uint8Array([1, 2, 3]));
stream.next(new Uint8Array([4, 5, 6]));
stream.next(null); // ends the stream

The function creates a "stream" based on an AsyncGenerator, to write data in a file.

INFO: I know that Streams already exists: NodeJS and StreamAPI. This is just an example, but still, creating "streams" from AsyncGenerator is sometimes pretty convenient and useful.