dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.09k stars 1.56k forks source link

Make yield an expression, not a statement, and let the iterator provide the next value. #32831

Open uehaj opened 6 years ago

uehaj commented 6 years ago

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:

Iterable func() sync* {
 var x = yield 33; // this is error with current Dart
}

Where the return value of yield is expected to be given from iterator side, for example:

func().moveNext("hello");  // this is an error with current Dart. moveNext cannot take parameter.

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:

lrhn commented 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
}
uehaj commented 6 years ago

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);
}
cowboyd commented 5 years ago

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:

It would really be awesome if Dart could do this as it would make implementing structured concurrency much more practical to implement.

cowboyd commented 4 years ago

Any progress on this? It would be a killer feature.