Some patterns become easier to write with the generator syntax, but it's not really adding expressive power. (Unlike futures for instance)
Generators allow you to write a state machine as a procedural function whose local variables are automatically suspended when you yield and restored when the caller returns control to the generator. They compile this down to, as you said, an object with a call method. But that transformation is as nontrivial as any compilation/transpilation project.
The advantage of generators over arrays is that you don't have to allocate a large array, so you can handle large numbers of elements while keeping a lower memory footprint.
They can also hide batching/chunking logic. You could have code that repeatedly downloads of separate JSON files with 200, or 500, or 5000 elements at a time, then return a generator that goes through each element at a time. The consuming code will be none the wiser.
The most common application is iteration (lazy producers or transformers), but they're also used for their ability to suspend and resume function execution (interruptible functions)
- pytest fixtures use generators to setup, allow the test to run (yielding a value or not), and teardown
- contextlib.contextmanager uses generators for similar purposes of entering a context, allowing the wrapped code to run, and then exiting the context
They can also act as cheap state managers, especially with the ability to inject data back into the generator, so they can be used to implement a multi-step function where the user needs multiple points of interaction, without needing a bunch of callbacks, or complicated state management (especially without the benefit of sum types or static state machines)
You can implement basic operators like map, filter, take, etc. over generators to create pipelines of operations. Very neat abstraction to work with, but like rx, can quickly get hard to reason about.
Recently I wrote some tooling to read, do some operations, and write hundreds of thousands of files locally. Using generators solved having to think about not loading too much stuff into memory since it only yields files when consumed. Also allows you simply implement stuff like batching, like running X requests to a server at a time, and only starting the next batch once the first one is done.
Glossing over this fact leads to a flawed understanding, not a deeper one.
Before async/await, generators were the only way to express async flow control in a synchronous way. Libraries like “co” were really popular at the time.
There are some really downside to async/await which is why redux-saga continued to use generators. Even in 2023, me and a couple others are experimenting with deliminited continuations using generators as the foundation.
A couple of libraries we have built:
- https://github.com/thefrontside/continuation
- https://github.com/thefrontside/effection/tree/v3
- https://github.com/neurosnap/starfx
The last one intends to replace redux-saga using DCs.
Here’s a presentation I gave recently talking about DCs in typescript: https://youtu.be/uRbqLGj_6mI?si=XI0JNMKMoO2VHMvM
A generator is nice here because it makes it easy for the code that converts the JSON for transmission to wait until the OS has transmitted the previous chunks -- which helps avoid catastrophic back pressure and runaway memory use.
For example, do you have a stream of chunks, and want to turn it into a stream of lines? The implementation of this with generators is super clean and readable, because it’s like you’re just iterating over a list.
The generator function keeps track of the current page.
This allows you to use a `for await` loop which is quite concise.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
You can do it without generator functions, but generator functions allows the caller to not have to know about the underlying pagination API.
> Generators are a special type of function that can return multiple pieces of data during its execution. Traditional functions can return multiple data by using structures like Arrays and Objects, but Generators return data whenever the caller asks for it, and they pause execution until they are asked to continue to generate and return more data.
Applications of generators? I have only used Redux-Saga[1]. Can't even think of other libraries that use them, but would be interested in learning.
[1]: https://redux-saga.js.org/