3 ms·
I thought coroutines don’t solve the function coloring problem considering we do have coroutines but combinatorial explosion due to function coloring is an issu
by vlovich123 3y ago
I thought coroutines don’t solve the function coloring problem considering we do have coroutines but combinatorial explosion due to function coloring is an issue. In other words, I need a try_async_map function if I want to support a map operation that has failability and async (as I need a duplicate try_map vs map to support just failability).
Whether this should be solved by a formal generic algebraic effects system or generic keywords is an open question but unless I’m mistaken this is a real problem that can’t be solved with coroutines.
The article also highlights an advantage for generic algebraic effects by reducing the cost of async code by not constructing the Waker until there’s a suspension point. I don’t understand the proposals well enough to know if generic keywords would solve this too, but it’s certainly worth considering.
I’m less bearish on new language mechanics if they solve a class of problems very consistently and improve efficiency. Complexity of usage is a concern vs something like generic keywords, but honestly self consistent mechanics like this might be less complex to master than generic keywords in the long run. There’s precedent here for example with macros which is a pretty complex subsystem but it’s carved out by experts providing libraries for the most part and I would expect similar behavior here where library code might be more complex in implementation but there’s no code duplication and the user code gets a simpler .map function usable in any context. There will also be plenty of copy-passable examples to learn from the std library APIs.
- anonymoushn 3y agoLots of coroutine implementations solve this problem, see maybe Lua or greenlet or Byuu's libco. Shipping async as-is and delaying async-in-trait until 1 month ago was a deliberate choice to make all libraries less composable.
- Rusky 3y ago> The article also highlights an advantage for generic algebraic effects by reducing the cost of async code by not constructing the Waker until there’s a suspension point. No, it doesn't. It highlights that as an advantage of one specific implementation style, while suggesting a different implementation for Rust.
- duped 3y agoIf you could spawn a coroutine and pass it around as a value, you could implement your effects just by calling coroutine.resume(...), coroutine.yield(...). The reason this is more powerful abstraction than async/await is that it's colorless - structs that could be sync or async just need a field that's Option<Coroutine> and when they want to be "effectful" they just do something like if let Some(async) = self.async.as_mut() { let next_arg = async.yield(next_result); } Consider deserializing JSON with serde_json over a TCP socket. There's no need for an AsyncRead because the asynchronicity isn't colorful - internally in the implementation of std::io::Read, it just needs to check if there is a coroutine to yield to. I'm less compelled about code duplication for trivial stuff like try_async_map, whereas being able to duct tape together major libraries with a few lines of code internally is much more compelling. And solves real problems, the previous example is actually a problem faced in codebases today. I can think of a few arguments against this, but I don't see it as structurally that different from algebraic effects.
- anonymoushn 3y agoI agree that try_async_map is whatever, but currently if you want to call several fallible functions from different libraries and propagate the errors you get into a bit of a mess.