The idiomatic way to implement generators (yield) in Golang for recursive functions

前端 未结 3 1432
难免孤独
难免孤独 2020-12-13 14:37

[ Note: I read Python-style generators in Go, this is not a duplicate of it. ]

In Python / Ruby / JavaScript / ECMAScript 6, generator functions could be written usi

3条回答
  •  醉酒成梦
    2020-12-13 14:54

    I agree with icza's answer. To summarize, there are are two alternatives:

    1. mapping function: Use a callback to iterate over a collection. func myIterationFn(yieldfunc (myType)) (stopIterating bool). This has the disadvantage of ceding the control flow to myGenerator function. myIterationFn is not a Pythonic generator because it doesn't return an iterable sequence.
    2. channels: Use a channel and be wary of leaking goroutines. It's possible to transform myIterationFn into a function that returns an iterable sequence. The following code provides an example of such a transformation.
    myMapper := func(yield func(int) bool) {
        for i := 0; i < 5; i++ {
            if done := yield(i); done {
                return
            }
        }
    }
    iter, cancel := mapperToIterator(myMapper)
    defer cancel() // This line is very important - it prevents goroutine leaks.
    for value, ok := iter(); ok; value, ok = iter() {
        fmt.Printf("value: %d\n", value)
    }
    

    Here's a complete program as an example. mapperToIterator does the transformation from a mapping function to a generator. Go's lack of generics requires casting from interface{} to int.

    package main
    
    import "fmt"
    
    // yieldFn reports true if an iteration should continue. It is called on values
    // of a collection.
    type yieldFn func(interface{}) (stopIterating bool)
    
    // mapperFn calls yieldFn for each member of a collection.
    type mapperFn func(yieldFn)
    
    // iteratorFn returns the next item in an iteration or the zero value. The
    // second return value is true when iteration is complete.
    type iteratorFn func() (value interface{}, done bool)
    
    // cancelFn should be called to clean up the goroutine that would otherwise leak.
    type cancelFn func()
    
    // mapperToIterator returns an iteratorFn version of a mappingFn. The second
    // return value must be called at the end of iteration, or the underlying
    // goroutine will leak.
    func mapperToIterator(m mapperFn) (iteratorFn, cancelFn) {
        generatedValues := make(chan interface{}, 1)
        stopCh := make(chan interface{}, 1)
        go func() {
            m(func(obj interface{}) bool {
                select {
                case <-stopCh:
                    return false
                case generatedValues <- obj:
                    return true
                }
            })
            close(generatedValues)
        }()
        iter := func() (value interface{}, notDone bool) {
            value, notDone = <-generatedValues
            return
        }
        return iter, func() {
            stopCh <- nil
        }
    }
    
    func main() {
        myMapper := func(yield yieldFn) {
            for i := 0; i < 5; i++ {
                if keepGoing := yield(i); !keepGoing {
                    return
                }
            }
        }
        iter, cancel := mapperToIterator(myMapper)
        defer cancel()
        for value, notDone := iter(); notDone; value, notDone = iter() {
            fmt.Printf("value: %d\n", value.(int))
        }
    }
    

提交回复
热议问题