Search code examples
pythondartgenerator

How to get Dart generator functions to act like Python?


I come from a Python, and Irecall that Python generators returns values through the keyword yield:

def generate_num() {
    n = 0
    while True:
        yield n
print(generate_num()) # Outputs 0
print(generate_num()) # Outputs 1
print(generate_num()) # Outputs 2
...

While learning Dart, I came across that Dart does it a little bit differently. So in the following code:

void main() {
    generate_num().takeWhile((val) {
        if (val == 2) {
            return false;
        }
        return true;
    }).forEach(print);
}

Iterable<int> generate_num() sync* {
    int n = 0;
    while(true) {
        yield n;
    }
}

When I run the program, it gets stuck on the while loop! A call to the function yields all possible values, but it's unable to because the while loop is set to true. This seems very different in Python where a single call would yield a single value, not all values. Is there way in Dart create generators that behave like Python?


Solution

  • The reason that this is happening here is that forEach is going through every element of your Iterable, and well, that would be an infinite amount in your case.
    The Dart version of your Python example would probably look something like this (moving the iterator one element at a time).

    void main() {
      final iterator = generateNum().iterator;
    
      print((iterator..moveNext()).current);
      print((iterator..moveNext()).current);
      print((iterator..moveNext()).current);
    }
    
    Iterable<int> generateNum() sync* {
      int n = 0;
      while(true) {
        yield n++;
      }
    }
    

    Note that takeWhile evaluates lazily, so the following will succeed and you can just apply it to your Iterable and use the iterator (final iterator = generateNum().takeWhile(...).iterator):

    void main() {
      print(generateNum().takeWhile((val) {
        if (val == 2) {
          return false;
        }
    
        return true;
      }).first);
    }