Open uehaj opened 6 years ago
This is possible, and even without being breaking.
We could change yield
to be an expression and change the Iterator
created by a sync*
function to have an optional argument on moveNext
which is passed back to the yield
.
Maybe:
abstract class CoIterable<E, V> extends Iterable<E> {
Coroutine<E, V> get iterator;
}
abstract class Coroutine<E, V> implements Iterator<E> {
bool moveNext([V value]);
}
Then you could do:
Coiterable<int, int> sequencer([int start = 0]) sync* {
int counter = start;
while (true) {
counter = ((yield counter) ?? counter) + 1;
}
}
main() {
var c = sequencer(10).iterator;
c.moveNext();
print(c.current); // 10
c.moveNext();
print(c.current); // 11
c.moveNext(20);
print(c.current); // 21
c.moveNext();
print(c.current); // 21
}
As a counter-point, it shows a very different usage pattern than what we normally do with iterators in Dart. In practice, Dart programs never actually use iterators directly, they are either iterated using for(..in..)
or consumed by library code. That suggests that coroutines like this is a different concept, not just a specialization of iterators, and it might warrant its own syntax and behavior, e.g.,:
coroutine int sequencer([int input]) {
int counter = input;
while (true) {
counter = (yield counter) ?? counter + 1;
}
}
main() {
int Function([int]) c = new sequencer;
print(c(10)); // 10;
print(c()); // 11
print(c(20)); // 20
print(c()); // 21
}
Thanks comment. About the counter-point you mentioned, If you let me explain it further, I assume use coroutine to implement Dart version of redux-saga, which is one of very popular middleware for React-Redux. In this use case, caller or coordinator of coroutines is implemented within library/middleware, so Dart programmer do not need to be conscious about caller syntax/semantics of coroutine.
As a programmer who write callee side of coroutine, I am accustomed with redux-saga's saga definition, following code looks no strange (just example). Similarity to redux-saga is rather preferable.
Iterable<Effect> rootSaga([msg]) sync* {
yield ForkEffect(saga2);
while (true) {
yield WaitEffect(3);
}
}
Iterable<Effect> saga2([msg]) sync* {
yield ForkEffect(saga3);
while (true) {
yield WaitEffect(3);
yield PutEffect(new Action("ACTION_TYPE1", null));
}
}
Iterable<Effect> saga3([Msg]) sync* {
while (true) {
var action = yield TakeEffect(new Action("ACTION_TYPE1", null));
:
}
}
main() {
var effectManager = new EffectManager();
effectManager.startSaga(rootSaga);
}
I'm also interested a co-routine based approach for structured concurrency similar to redux-saga. In order to implement this, there would need to be more than just a moveNext()
that took an argument. You would need the ability to raise errors in a routine, as well as return immediately from a routine and release any resources it is holding onto.
This is done in JavaScript with the:
throw(exception)
to error out a generation.return(value)
return immediately (cancel) a generation.It would really be awesome if Dart could do this as it would make implementing structured concurrency much more practical to implement.
Any progress on this? It would be a killer feature.
dart --version
) Dart VM version: 2.0.0-dev.32.0 (Thu Mar 1 18:39:53 2018 +0100) on "macos_x64"Problem
In dart language, generator's yield language construct is a statement and cannot gain any value. Although in Python and JavaScript (ES 2015), yield is a expression and can get value like:
Where the return value of yield is expected to be given from iterator side, for example:
To get value from yield is extremely valuable when use generator to implement coroutine, moreover actually it is necessarily.
I hope we can use "yield expression" in future Dart release.
References:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield rv Returns the optional value passed to the generator's next() method to resume its execution.
https://stackoverflow.com/questions/12637768/python-3-send-method-of-generators