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?