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
- Was ist die idiomatischen Weg für die Implementierung von Generatoren-wie dies?
- Idiomatically, wer sollte verantwortlich sein
close()
den Kanal - die Bibliothek-Funktion, oder die Anrufer? - 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
}
}
}
- 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 solltepanic
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? - 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, zuclose()
den Kanal, könnte es nicht sein markiert als "receive-only", wie derclose()
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
Du musst angemeldet sein, um einen Kommentar abzugeben.
I. Alternativen
Vorwort: werde ich ein viel einfacher generator, denn das problem betrifft nicht den generator Komplexität, sondern die Signale, die zwischen generator und Verbraucher und den Ruf der Verbraucher selbst. Dieser einfache generator erzeugt die zahlen von
0
zu9
.1. Eine Funktion, mit der Wert -
Erstellen-consumer-pattern ist viel sauberer mit einem einfachen Verbraucher Funktion übergeben, die hat auch den Vorteil, dass es einen Wert zurück melde, wenn die Abtreibung oder jede andere Maßnahme erforderlich ist.
Und da im Beispiel nur ein Ereignis signalisiert werden ("abort"), die Verbraucher-Funktion haben
bool
zurück geben, Signalisierung, wenn der Abbruch wird erforderlich.Also sehen dieses einfache Beispiel mit einem Verbraucher-Funktion übergebenen Wert an den generator:
Ausgang (versuchen Sie es auf die Gehen Spielplatz):
Hinweis, dass der Verbraucher (
process
) muss nicht sein, ein "local" - Funktion, es kann deklariert werden, außerhalb vonmain()
, z.B. es kann eine Globale Funktion oder eine Funktion aus einem anderen Paket.Den potenziellen Nachteil dieser Lösung ist, dass es verwendet nur 1 goroutine sowohl für die Erzeugung und Konsum-Werte.
2. Mit Kanälen
Wenn Sie wollen immer noch, es zu tun mit den Kanälen, Sie können. Beachten Sie, dass seit der Kanal ist, durch den generator erzeugt wird, und da die Verbraucher durchläuft in einer Schleife die Werte aus den empfangenen Kanal (idealerweise mit
for ... range
Konstrukt), es ist der generator der Verantwortung, schließen Sie den Kanal. Die Abrechnung mit den dieser ermöglicht es Ihnen auch, um wieder eine " receive-only channel.Und ja, die Schließung des zurückgegebenen Kanal in der generator ist am besten als eine latente Aussage, auch wenn der generator Panik, der Verbraucher wird nicht blockiert. Aber beachten Sie, dass diese latente schließen, ist nicht in der
generate()
Funktion, sondern in der anonymen Funktion gestartet vongenerate()
und ausgeführt, als einer neuen goroutine; sonst ist der Kanal geschlossen werden würde, bevor es zurückgenerate()
- überhaupt nicht nützlich...Und wenn Sie wollen, um ein signal-generator von dem Verbraucher (z.B. um den Vorgang abzubrechen und keine weiteren Werte), die Sie verwenden können, z.B. einen anderen Kanal, der an den generator. Da der generator nur "hören" auf diesem Kanal, es können auch erklärt werden, wie eine receive-channel nur auf den generator. Wenn Sie nur brauchen, um zu signalisieren ein Ereignis (Abbruch in unserem Fall), keine Notwendigkeit, senden Sie alle Werte, die auf diesem Kanal, eine einfache in der Nähe wird es tun. Wenn Sie brauchen, um das signal mehrere Ereignisse, es kann getan werden, indem Sie wirklich Wert senden auf diesem Kanal, wird das Ereignis /die Aktion durchgeführt werden soll (wo Abbrechen kann eines von mehreren Ereignissen).
Und Sie können die
select
- Anweisung wie die idiomatischen Weg zu senden die Werte auf den zurückgegebenen Kanal und beobachten Sie die Kanal geleitet, um die generator.Hier ist eine Lösung mit einem
abort
Kanal:Ausgang (versuchen Sie es auf die Gehen Spielplatz):
Der offensichtliche Vorteil dieser Lösung ist, dass es bereits verwendet 2 goroutines (1, die Werte generiert, 1, verbraucht/verarbeitet), und es ist sehr einfach zu erweitern ist zur Verarbeitung der generierten Werte mit einer beliebigen Anzahl von goroutines, wie der Kanal zurückgegeben, die durch den generator verwendet werden kann, aus mehreren goroutines gleichzeitig - Kanäle sicher zu sein, empfangen von gleichzeitig, data races nicht auftreten können, durch design; mehr Lesen: Wenn ich die Kanäle richtig muss ich Mutexe verwenden?
II. Antworten auf unadressierte Fragen
Einer "uncaught" Panik auf eine goroutine wird am Ende der Ausführung der goroutine aber nicht zu einem problem in Bezug auf Ressourcen-Leck. Aber wenn die Funktion ausgeführt als separates goroutine würden Ressourcen frei (in nicht-latente Aussagen) zugeordnet, indem es im Falle der nicht-Panik, dieser code wird offensichtlich nicht ausgeführt, und es werden Ursache-Ressource-Leck zum Beispiel.
Haben Sie nicht beobachtet, weil das Programm endet, wenn der Haupt-goroutine beendet (und es nicht warten, für andere nicht-Haupt-goroutines zu beenden - damit die anderen goroutines nicht die chance bekommen, in Panik zu geraten). Sehen Skillung: Programmausführung.
Aber wissen, dass
panic()
undrecover()
für außergewöhnliche Fälle, Sie sind nicht gedacht für solche Anwendungsfälle wie die Ausnahmen undtry-catch
Blöcke in Java. Panik sollte vermieden werden, die durch Fehler zurückgeben (und behandeln!) zum Beispiel, und Panik sollte auf jeden Fall nicht verlassen, die "Grenzen" der Pakete (z.B.panic()
undrecover()
gerechtfertigt sein können, verwendet werden in einem Paket-Implementierung, aber in Panik Zustand sein sollte, "gefangen" in dem Paket und lassen Sie nicht aus ihm heraus).abort
geschlossen ist, obwohl dies erfordert eine sehr unglückliche timing.break
in der Verbraucher nach denclose()
(es war nicht erkennbar, mit aktuellen goroutine scheduling weil Ungepuffert-Kanal wurde ingenerate()
). Mitbreak
wird sichergestellt, dass keine weiteren Elemente werden verarbeitet, und dieclose(abort)
wird dafür sorgen, die anderen goroutine kann kündigen, wie gut.Meiner Meinung nach in der Regel Generatoren sind nur Wrapper um die Schließung intern. So etwas
Sieht es nicht so elegant wie "für die reichen", sondern ganz klar semantisch und syntaktisch für mich. Und es funktioniert http://play.golang.org/p/fz8xs0RYz9
permutation
zurückgeben kann manint
auf eine Zeit gegeben, einint
. Um zu demonstrieren, Ihre Annahme, hier werden mehrere läuft:permutation(1)
gibt, sagen,20
, dann füttern Sie20
den nächsten Anruf;permutation(20)
gibt, sagen,15
, dann füttern Sie15
den nächsten Anruf. Leider ist das nicht wie "rekursive Funktion" funktioniert.abcd
,abdc
,acbd
,acdb
, ...,dcba
wenn es heißtpermutation([]string{"a", "b", "c", "d"})
. Danke!Ich Stimme mit icza Antwort. Um zusammenzufassen, gibt es zwei alternativen:
func myIterationFn(
yield
func (myType)) (stopIterating bool)
. Dies hat den Nachteil, dass eine Abtretung der Ablaufsteuerung zumyGenerator
Funktion.myIterationFn
ist nicht ein Pythonic generator, da spielt es keine Rückkehr einer wiederholenden Sequenz.myIterationFn
in eine Funktion zurückgibt, die einen wiederholenden Sequenz. Der folgende code zeigt ein Beispiel einer solchen transformation.Hier ist ein vollständiges Programm als Beispiel.
mapperToIterator
hat die transformation von einem mapping-Funktion zu einem generator. Gehen fehlen von Generika erfordert eine Umwandlung voninterface{}
zuint
.