This is an excellent, very insightful post.
When Erik Meijer worked with Gilad Bracha and others on the Dart team to bring async to Dart, he approached with a very similar "lets look at the table of all the combinations and make sure we didn't leave any holes". In this case, the two axes (effects) he considered were asynchrony and iteration. So Dart supports all four combinations:
* Synchronous single-value functions are just normal function declarations. Within one, you use `return` to yield the one value.
* Synchronous multi-value functions are generators, marked `sync`, which return a `Iterable<T>`. Within one, you use `yield` and `yield` to emit values.
* Asynchronous single-value functions are marked `async` and return a `Future<T>`. Within on, you use `await` to pause on asynchronous operations and consume values. A `return` valued is implicitly wrapped in a future.
* Asynchronous multi-value functions are marked `async` and return a `Stream<T>`. Within one, you can use both `yield` and `yield` to emit values and `await` to suspend and consume asynchronous values.
We also have an asynchronous `await for` statement that can be used to imperatively consume a stream.
There's no third axis for fallibility, because Dart chooses to comprehensively use exceptions for operations that fail (which I quite like).
Overall, it's a really rich, comprehensive approach to the problem. I have mixed feelings about it. It really does cover all the bases. But, in practice, `async` functions and `await for` are very* rarely used but add a lot of complexity to the language and engineering burden to the implementations.
When Erik Meijer worked with Gilad Bracha and others on the Dart team to bring async to Dart, he approached with a very similar "lets look at the table of all the combinations and make sure we didn't leave any holes". In this case, the two axes (effects) he considered were asynchrony and iteration. So Dart supports all four combinations:
* Synchronous single-value functions are just normal function declarations. Within one, you use `return` to yield the one value.
* Synchronous multi-value functions are generators, marked `sync`, which return a `Iterable<T>`. Within one, you use `yield` and `yield` to emit values.
* Asynchronous single-value functions are marked `async` and return a `Future<T>`. Within on, you use `await` to pause on asynchronous operations and consume values. A `return` valued is implicitly wrapped in a future.
* Asynchronous multi-value functions are marked `async` and return a `Stream<T>`. Within one, you can use both `yield` and `yield` to emit values and `await` to suspend and consume asynchronous values.
We also have an asynchronous `await for` statement that can be used to imperatively consume a stream.
There's no third axis for fallibility, because Dart chooses to comprehensively use exceptions for operations that fail (which I quite like).
Overall, it's a really rich, comprehensive approach to the problem. I have mixed feelings about it. It really does cover all the bases. But, in practice, `async` functions and `await for` are very* rarely used but add a lot of complexity to the language and engineering burden to the implementations.