Reputation: 2063
Once you have coroutines you can create pipelines (haskell: pipes, conduits; python: generators) or cooperative event loops (python: curio). Once you have futures, it appears you can do the same; pipelines (rust: futures-rs) and event loops (rust: tokio). Since futures aren't cooperative they require a callback-based (even poll-based futures require callbacks) scheduler to execute blocking tasks within a thread or process pool. What benefits are there to combining futures (library-level) with coroutines (language-level) as these languages do: (python: asyncio), (rust: rfc), (ecmascript 6+). Fundamentally they seem to be conflicting solutions to the same problem.
I'm not looking for a pro/con comparison, and I don't buy the argument that futures are "one-shot" coroutines. Just look at rust, which built an entire state-machine-based event framework using just futures. I want to know why python/asyncio and javascript both require coroutines together with futures. Why rust is planning on adding coroutines to its futures? Does it have to do with composability of events? Or the implicit stack of coroutines versus the explicit stack of continuation-passing futures? Not that I completely understand this argument, as both futures and coroutines are implemented using continuations... Or does it have something to do with direct vs indirect style?
Upvotes: 1
Views: 1232
Reputation: 58785
These are all different (though related) ideas with different amounts of power.
A future is an abstraction that lets you begin a process and then yield back to a handler, that is chosen by the original caller, when the process is done.
A generator is more powerful than a future because it can yield multiple times. You can implement futures on top of generators.
A coroutine is more powerful than a generator because it can choose who to yield to, instead of only to the caller. For example it can yield to another coroutine. You can implement generators on top of coroutines.
Why would you use the less powerful tool, when more powerful ones are available? Sometimes the less powerful tool is the right tool for the job. It's useful to statically encode your program's invariants using types, because it can give you certainty about what something can't do.
For example, when making a REST call to a remote server, a future is probably sufficient. If the REST client exposed a generator, you'd have to deal with the possibility that it could yield multiple times, even though you know there is only going to be one result. If it exposed a coroutine, you'd have to consult the documentation to work out exactly how you're supposed to interact with it - even though you actually only need to do one thing, which is obvious when you're dealing with a future.
Upvotes: 2