Die idiomatischen Möglichkeit zur Implementierung von Generatoren (yield) in Golang für rekursive Funktionen

[ Anmerkung: ich lese Python-Stil-Generatoren Gehen, dies ist nicht eine Kopie der. ]

In Python /Ruby /JavaScript /ECMAScript 6, generator-Funktionen geschrieben werden kann, mit der yield Schlüsselwort zur Verfügung gestellt, die durch die Sprache. Im Gehen, es könnte simuliert werden, mit einer goroutine und einen Kanal.

Den Code

Folgende code zeigt, wie eine permutation-Funktion (abcd, abdc, acbd, acdb, ..., dcba) umgesetzt werden könnte:

//$src/lib/lib.go

package lib

//private, starts with lowercase "p"
func permutateWithChannel(channel chan<- []string, strings, prefix []string) {
    length := len(strings)
    if length == 0 {
        //Base case
        channel <- prefix
        return
    }
    //Recursive case
    newStrings := make([]string, 0, length-1)
    for i, s := range strings {
        //Remove strings[i] and assign the result to newStringI
        //Append strings[i] to newPrefixI
        //Call the recursive case
        newStringsI := append(newStrings, strings[:i]...)
        newStringsI = append(newStringsI, strings[i+1:]...)
        newPrefixI := append(prefix, s)
        permutateWithChannel(channel, newStringsI, newPrefixI)
    }
}

//public, starts with uppercase "P"
func PermutateWithChannel(strings []string) chan []string {
    channel := make(chan []string)
    prefix := make([]string, 0, len(strings))
    go func() {
        permutateWithChannel(channel, strings, prefix)
        close(channel)
    }()
    return channel
}

Hier ist, wie es verwendet werden könnte:

//$src/main.go

package main

import (
    "./lib"
    "fmt"
)

var (
    fruits  = []string{"apple", "banana", "cherry", "durian"}
    banned = "durian"
)

func main() {
    channel := lib.PermutateWithChannel(fruits)
    for myFruits := range channel {
        fmt.Println(myFruits)
        if myFruits[0] == banned {
            close(channel)
            //break
        }
    }
}

Hinweis:

Den break - Anweisung (kommentierte oben) wird nicht benötigt, da close(channel) Ursachen range zurück false in der nächsten iteration der Schleife wird beendet.

Das Problem

Wenn der Anrufer braucht nicht alle Permutationen, die es braucht, um close() den Kanal explizit, oder der Kanal nicht geschlossen wird, bis das Programm beendet wird (Ressource Leckage Auftritt). Auf der anderen Seite, wenn der Anrufer die Bedürfnisse aller Permutationen (d.h. die range loops bis zum Ende), MUSS der Anrufer NICHT close() den Kanal. Es ist, weil close()-ing, eine bereits abgeschlossene-Kanal bewirkt eine runtime-Panik (siehe hier in der spec). Jedoch, wenn die Logik, um zu bestimmen, ob Sie aufhören sollte, oder nicht, ist nicht so einfach, wie oben gezeigt, ich denke, es ist besser, verwenden Sie defer close(channel).

Die Fragen

  1. Was ist die idiomatischen Weg für die Implementierung von Generatoren-wie dies?
  2. Idiomatically, wer sollte verantwortlich sein close() den Kanal - die Bibliothek-Funktion, oder die Anrufer?
  3. Ist es eine gute Idee, um zu ändern mein code wie unten, so dass der Aufrufer ist dafür verantwortlich defer close() den Kanal, egal was ist?

In der Bibliothek, ändern Sie diese:

    go func() {
        permutateWithChannel(channel, strings, prefix)
        close(channel)
    }()

zu diesem:

    go permutateWithChannel(channel, strings, prefix)

In der Anrufer, ändern Sie diese:

func main() {
    channel := lib.PermutateWithChannel(fruits)
    for myFruits := range channel {
        fmt.Println(myFruits)
        if myFruits[0] == banned {
            close(channel)
        }
    }
}

zu diesem:

func main() {
    channel := lib.PermutateWithChannel(fruits)
    defer close(channel)    //<- Added
    for myFruits := range channel {
        fmt.Println(myFruits)
        if myFruits[0] == banned {
            break           //<- Changed
        }
    }
}
  1. Trotz ist es nicht beobachtbar durch Ausführung von code von oben, und die Korrektheit des Algorithmus ist nicht betroffen, wenn der Anrufer close()s der Kanal, der goroutine läuft der code der Bibliothek sollte panic wenn er versucht zu senden, um der geschlossen-Kanal-in der nächsten iteration, wie dokumentiert hier in der spec, wodurch es zu beenden. Bedeutet das irgendwelche negativen Nebenwirkungen?
  2. Die Signatur der library-Funktion ist func(strings []string) chan []string. Idealerweise ist der Rückgabetyp sollte <-chan []string zu beschränken, es zu erhalten-nur. Allerdings, wenn es der Anrufer, der dafür verantwortlich ist, zu close() den Kanal, könnte es nicht sein markiert als "receive-only", wie der close() built-in-Funktion funktioniert nicht auf " receive-only-Kanäle. Was ist die idiomatischen Umgang mit diesem?
  • Ich bin mir nicht sicher, wie idiomatische Kanäle & goroutines wirklich sind für diesen Zweck; ich finde, dass insbesondere Menschen, die neu sind zu Gehen, sind oft ein bisschen zu verliebt mit Ihnen und verwenden Sie Sie, wo Sie sind nicht besonders nützlich. Für Generatoren, die ich verwenden in der Regel einfache, streiten-freie Verschlüsse.
  • beachten Sie, dass einfache Verschlüsse nicht auf die gleiche Weise, Generatoren tun. Generatoren, die nur Arbeit machen und zu produzieren einen mehr-Wert jedes mal, wenn Sie genannt werden, den Status zu halten in der Zwischenzeit. Ich weiß nicht von irgendeiner Weise zu bekommen, einen Abschluss (ohne Kanäle und einer goroutine) blockiert, bis erneut aufgerufen.
  • Ich Stimme mit @AdamSmith. Auch ein wichtiger use-case für einen generator ist die asynchrone Verarbeitung mit blocking I/O. Für eine Schließung ist nicht genug; Sie müssen eine goroutine. Also ich mag das Muster sehr. Und wenn der Anrufer ist bereit, bis zum Abschluss ausgeführt, das Muster ist sogar noch besser, da der Anrufer kann einfach iteriert über den Kanal.
  • Bitte beachten Sie, dass Kanäle, die nicht geschlossen werden müssen bereinigt werden und nicht Auslaufen. Als solche, Sie könnten erzeugen alle Werte und speichert Sie in einer gepufferten Kanal. Wenn es keine weiteren Verweise, dann wäre es gereinigt werden. (Hinweis: Sie können auch schließen Sie den Kanal nach dem einsetzen aller Werte, dies würde es ermöglichen, für die Nutzung des Bereichs auf der reader-Seite)
  • Goroutines tun, Leck, obwohl.
  • absolut, und wir sollten auf jeden Fall verbringen eine gute Menge an Zeit, um zu vermeiden, dass. Meine Kommentare waren nur über TV und nicht brauchen, um Sie zu schließen

Schreibe einen Kommentar