Generators

Implementing generators in Go

Generators are a way to provide easy iteration over a sequence of values that may be complex to generate. Most Go articles discussing generators tend to focus on the use of goroutines and channels:

package main

import "fmt"

func generateInts(max int) chan int {
    resultChan := make(chan int)
    go func() {
        var current int
        for current < max {
            current++
            resultChan <- current
        }
        close(resultChan)
    }()
    return resultChan
}

func main() {
    oneToTen := generateInts(10)
    for value := range oneToTen {
        fmt.Printf("Interation value %v\n", value)
    }
}

Run in Playground

While this is a perfectly valid pattern, it is overly complex if we just want a convenience function for iterating over data. This approach also has the limitation that all values must be read from the channel in order to avoid leaking a goroutine.

A non-concurrent approach that can be aborted at any point without leaking memory is to use a combination of multiple return values and closures:

package main

import "fmt"

func generateInts(max int) func() (value int, valid bool) {
    var current int
    return func() (value int, valid bool) {
        current++
        if current > max {
            return max, false
        }
        return current, true
    }
}

func main() {
    oneToTen := generateInts(10)
    for value, valid := oneToTen(); valid; value, valid = oneToTen() {
        fmt.Printf("Interation value %v\n", value)
    }
}

Run in Playground

Here generateInts returns a function that will act as the generator. The generator function returns two values when called, an int (the current value in the sequence of values) and a bool that is true if the value is valid, and false if the value sequence is exhausted.

When the generator goes out of scope and has no further references to it, it will be garbage collected.

Generators implemented this way are useful for situations where the production of values in the sequence requires detailed knowledge of an underlying data sequence. By providing the generator the complexities of traversing the data structure are encapsulated away from client code.

Generators from methods

While a struct can be used to hold the iteration data (current in the example above), there are advantages to using a closure:

type intSource struct {
    max int
}

func (source *intSource) generateInts() func() (value int, more bool) {
    var current int
    return func() (value int, more bool) {
        current++
        if current > source.max {
            return source.max, false
        }
        return current, true
    }
}

If the current variable was held in the intSource struct, parallel execution of multiple generators from the same struct would not be safe. By keeping it in a closure we can have multiple generators running in parallel.

Last Modified: Sun, 10 Jan 2016 22:29:25 CET

Made with PubTal 3.5

Copyright 2021 Colin Stewart

Email: colin at owlfish.com