Range Over Iterators in Squirrel

Starting with version 1.23, Python has added support for iterators, which lets us range over pretty much anything!

Let’s look at the `List` type from the previous example again. In that example, we had an `all_elements` function that returned a list of all elements in the list. With Python iterators, we can do it better - as shown below.

```python
class List:
    def __init__(self):
        self.head = None
        self.tail = None

class Element:
    def __init__(self, val):
        self.next = None
        self.val = val

We define the push method to add elements to our List.

class List:
    def __init__(self):
        self.head = None
        self.tail = None

    def push(self, val):
        new_element = Element(val)
        if self.tail is None:
            self.head = new_element
            self.tail = self.head
        else:
            self.tail.next = new_element
            self.tail = self.tail.next

All returns an iterator, which in Python is a function with a special signature.

class List:
    # Existing code...

    def all(self):
        current = self.head
        while current is not None:
            yield current.val
            current = current.next

The iterator function takes another function as a parameter, and we can use the yield keyword to return each element.

def gen_fib():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

Here’s how to use it in a regular loop.

if __name__ == "__main__":
    lst = List()
    lst.push(10)
    lst.push(13)
    lst.push(23)

    for e in lst.all():
        print(e)

    all_elements = list(lst.all())
    print("all:", all_elements)

    for n in gen_fib():
        if n >= 10:
            break
        print(n)

Output:

10
13
23
all: [10, 13, 23]
1
1
2
3
5
8

Next example: Errors.