55

I wanted to know what's the difference between this two. I find this SO post on javascript, Delegated yield (yield star, yield *) in generator functions

From what I understand, yield* delegates to the another generator and after the another generator stop producing values, then it resumes generating its own values.

Explanation and examples on the dart side would be helpful.

CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
mirkancal
  • 4,762
  • 7
  • 37
  • 75

4 Answers4

71

yield

It is used to emit values from a generator either async or sync.

Example:

Stream<int> getStream(int n) async* {
  for (var i = 1; i <= n; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

void main() {
  getStream(3).forEach(print);
}

Output:

1
2
3

yield*

It delegates the call to another generator and after that generator stops producing the values, it resumes generating its own values.

Example:

Stream<int> getStream(int n) async* {
  if (n > 0) {  
    await Future.delayed(Duration(seconds: 1));
    yield n; 
    yield* getStream(n - 1); 
  }
}

void main() {
  getStream(3).forEach(print);
}

Output:

3
2 
1 
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
  • What would happen if I would use `yield numbersDownFrom(n - 1)` instead of `yield* numbersDownFrom(n - 1);` inside of the generator ? – sonic Aug 27 '20 at 16:07
  • @sonic `yield*` is used to delegate the call to the generator, `yield numbersDownFrom(n-1)` won't make any sense because `yield` only yields the type you wanted to return in your `Stream`. – CopsOnRoad Aug 27 '20 at 16:15
  • I need to test it on my own. I was just curious if it will be flagged as error or it will do something unexpected. Thanks for answer. – sonic Aug 28 '20 at 10:44
  • There's an error in the beginning of the answer:the sentence 'It is used to emit values from a generator either async or sync.' should be 'It is used to emit values from a generator either async* or sync*.' See https://jelenaaa.medium.com/what-are-sync-async-yield-and-yield-in-dart-defe57d06381 – Jean-Pierre Schnyder Apr 01 '22 at 19:59
56

Short answer

  • yield provides a single value in an Iterable or a Stream.
  • yield* provides multiple values from another Iterable or Stream.

Iterable (synchronous) example

Take a look at how the following two iterables interact:

void main() {
  final myIterable = getValues();
  for (int value in myIterable) {
    print(value);
  }
}

Iterable<int> getValues() sync* {
  yield 42;
  yield* getThree();
  yield* [6, 7, 8];
  yield 24;
}

Iterable<int> getThree() sync* {
  yield 1;
  yield 2;
  yield 3;
}

Here's what's happening:

  • sync* creates an iterable, making it a synchronous generator function.
  • yield 42 provides a single value (42) for the getValues generator function.
  • yield* provides multiple values for getValues by requesting all of the iterable values from the getThree generator function.
  • Since a list is also iterable, yield* [6, 7, 8] yields each of the values in the list for getValues.
  • Finally, yield 24 provides another single value for getValues. This value is only provided after the previous yield* calls to the other iterables have completed.

Run the code above, and you'll see the following result:

42
1
2
3
6
7
8
24

Stream (asynchronous) example

Now have a look at the stream version:

Future<void> main() async {
  final myStream = getValues();
  await for (int value in myStream) {
    print(value);
  }
}

Stream<int> getValues() async* {
  yield 42;
  yield* getThree();
  yield* Stream.fromIterable([6, 7, 8]);
  yield 24;
}

Stream<int> getThree() async* {
  yield 1;
  yield 2;
  yield 3;
}

Notes:

  • async* creates a stream, making it an asynchronous generator function.
  • As before, yield provides a single value.
  • yield* provides multiple values from another stream.
  • yield* waits for the called stream to complete before going on.

Run that, and again you'll see the following output:

42
1
2
3
6
7
8
24

This answer is a complete rewrite of my original answer (see edit history), where I had grossly misunderstood the meaning of yield*, even after watching the Generator Functions - Flutter in Focus video. Thanks to @Hackmodford for correcting me in the comments.

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • `yield*` is not related to recursion. Your example just happens to use recursion. It can also yield to different streams. – Hackmodford May 23 '23 at 12:12
  • @Hackmodford, please explain more. I pulled the recursive part from the [Flutter team video](https://youtu.be/TF-TBsgIErY?list=PLjxrf2q8roU2HdJQDjJzOeO6J3FoFLWr2&t=274). I still don't understand it well though, especially if it isn't about recursion. – Suragch May 25 '23 at 06:40
  • It's just going to `yield*` the stream until it's done and then continue execution. So let's say you're writing a async* generator. You can `yield` values A, then B, then `yield*` an entirely different stream (say it emits C, and D, then finishes), then you could `yield` E, F, and finish. The output would be A,B,C,D,E,F and the C,D came from a different stream that only emits those values. Recursion not really related. – Hackmodford May 26 '23 at 14:21
  • 1
    @Hackmodford, That helps a lot. Thank you. I think I understand now. I've rewritten my answer, so if you have a chance, take a look and see if I have any more mistakes. – Suragch May 31 '23 at 07:47
  • 1
    Yup! This is my understanding of how it works. I have updated my vote. – Hackmodford Jun 01 '23 at 01:02
4

I have created a dart pad link to help people experiment:

Yield* is used to yield a whole iterable one value at a time with out using a loop.

These 2 functions do exactly the same thing, generates an iterable based on the start and finish values.

Iterable<int> getRangeIteration(int start, int finish) sync* {
  for(int i = start; i<= finish; i++){
  yield i;
  }
}

Iterable<int> getRangeRecursive(int start, int finish) sync* {
  if (start <= finish) {
    yield start;
    yield* getRangeRecursive(start + 1, finish);
  }
}

In the first implementation (yield i) would imply type Iterable which matches the function's return type.

now if in the second implementation instead of

 yield* getRangeRecursive(start + 1, finish);

if we did

yield getRangeRecursive(start + 1, finish);

we will get a compiler error:

The type 'Iterable<Iterable<int>>' implied by the 'yield' expression must be assignable to 'Iterable<int>'.

as you can see the yield wraps the Iterable with another Iterable<>, which makes the type Iterable<Iterable>. Which does not match the return type of the function.

If we have to do recursion without using yield* we will have to do this:

Iterable<int> getRangeRecursiveWithOutYieldStar(int start, int finish) sync* {
  if (start <= finish) {
    yield start;
    for (final val in getRangeRecursiveWithOutYieldStar(start + 1, finish)){
    yield val;
    }
  }
}

Which is messy and inefficient.

So I feel like in my opinion yield* flattens another generator function.

A few good resources: Generator Functions - Flutter in Focus video

Medium article: What are sync*, async*, yield and yield* in Dart?

abann sunny
  • 928
  • 12
  • 15
-1

Use the code below to understand why coding recursion without using yield* is really inefficient.

Iterable<int> getRangeYield(int start, int end) sync* {
  if (start <= end) {
    yield start;
    for (final int val in getRangeYield(start + 1, end)) {
      yield val;
    }
  }
}

Iterable<int> getRangeYieldAnalysed(int start, int end) sync* {
  if (start <= end) {
    print('before start $start');
    yield start * 10;
    print('after start $start');
    for (final int val in getRangeYieldAnalysed(start + 1, end)) {
      print('before val $val');
      yield val * 100;
      print('after val $val');
    }
  }
}

Iterable<int> getRangeYieldStar(int start, int end) sync* {
  // same output as getRangeYield()
  if (start < end) {
    yield* getRangeYieldStar(start + 1, end);
  }
  yield start;
}

Iterable<int> getRangeYieldStarAnalysed(int start, int end) sync* {
  // same output as getRangeYield()
  print('generator $start started');
  if (start < end) {
    yield* getRangeYieldStarAnalysed(start + 1, end);
  }
  yield start;
  print('generator $start ended');
}

Iterable<int> getRangeForLoop(int start, int end) sync* {
  // same output as getRangeYield()
  for (int i = start; i <= end; i++) {
    yield i;
  }
}

void main() {
  Iterable<int> it = getRangeYieldStarAnalysed(1, 4);
  print('main range obtained');

  for(int element in it) {
    print('el $element');
  };
}
Jean-Pierre Schnyder
  • 1,572
  • 1
  • 15
  • 24