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.


Solution 1: CopsOnRoad

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* str(n - 1);
  }
}

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

Output:

3
2 
1 


Solution 2: Suragch

Short answer

  • yield returns values from an Iterable or a Stream.
  • yield* is used to recursively call its Iterable or Stream function.

Examples

Let's look an some examples from the Generator Functions - Flutter in Focus video. We'll just look at the Iterable example, but it's similar for Streams.

Form 1

Here is an Iterable that counts from start to finish.

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

The yield keyword returns the next value on each iteration.

Form 2

Now let's refactor that function to be recursive. On the outside it still does the same thing as before:

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

This works but it's hard to read and not very efficient because of the loop.

Form 3

Now let's refactor it again using yield* (pronounced "yield star"):

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

It's still recursive, but now it's easier to read and is more efficient.


Solution 3: abann sunny

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?


Solution 4: Jean-Pierre Schnyder

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