Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
uehaj opened this issue Apr 10, 2018 · 4 comments
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). core-n type-enhancement A request for a change that isn't a bug

Comments

@uehaj
Copy link

uehaj commented Apr 10, 2018

  • Dart SDK Version (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:

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 lrhn added area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). type-enhancement A request for a change that isn't a bug labels Apr 10, 2018
@lrhn lrhn changed the title yield is statement, not expression Make yield an expression, not a statement, and let the iterator provide the next value. Apr 10, 2018
@lrhn
Copy link
Member

lrhn commented Apr 10, 2018

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
Copy link
Author

uehaj commented Apr 10, 2018

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);
}

@lrhn lrhn added the core-n label Dec 6, 2018
@cowboyd
Copy link

cowboyd commented Jul 18, 2019

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
Copy link

cowboyd commented Nov 6, 2019

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). core-n type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

3 participants