Heim >Backend-Entwicklung >Golang >Go-Teratoren verstehen
Viele Leute scheinen durch neu hinzugefügte Iteratoren in Go verwirrt zu sein, deshalb habe ich beschlossen, einen weiteren Artikel zu schreiben, um sie so einfach wie möglich zu erklären.
Zuerst denke ich, dass es wichtig ist zu verstehen, wie Iteratoren überhaupt von Go aufgerufen und verwendet werden, und es ist eigentlich ziemlich einfach. Nehmen wir den Slices.All-Iterator als Beispiel. So würden Sie diesen Iterator normalerweise verwenden:
package main import ( "fmt" "slices" ) func main() { slice := []string{ "Element 1", "Element 2", "Element 3", "Element 4", } for index, element := range slices.All(slice) { if index >= 2 { break } fmt.Println(index, element) } // Output: // 0 Element 1 // 1 Element 2 }
Und so sieht es tatsächlich aus:
package main import ( "fmt" "slices" ) func main() { slice := []string{ "Element 1", "Element 2", "Element 3", "Element 4", } slices.All(slice)(func (index int, element string) bool { if index >= 2 { return false // break } fmt.Println(index, element) return true // continue loop as normal }) // Output: // 0 Element 1 // 1 Element 2 }
Was passiert, ist, dass der Schleifenkörper „verschoben“ wird, um eine Funktion zu erhalten, die an den Iterator übergeben wird, während continue und break so umgewandelt werden, dass sie „true“ bzw. „false“ zurückgeben. return true wird auch am Ende der Schleife hinzugefügt, um zu signalisieren, dass wir das nächste Element erhalten möchten, sofern zuvor keine andere Entscheidung getroffen wurde.
Dies ist keine genaue Darstellung dessen, was der Compiler tut, und ich habe die Go-Implementierung nicht überprüft, um dies zu überprüfen, aber sie liefern aufgrund meiner Beobachtungen gleichwertige Ergebnisse.
Da Sie nun verstehen, wie sie aufgerufen werden, und erkennen, wie einfach es tatsächlich ist, wird es viel einfacher sein, zu verstehen, wie Sie Ihren eigenen Iterator und seine Ausführung erstellen.
Lassen Sie uns einen Debug-Iterator erstellen, der Debug-Meldungen für jeden Schritt der Iterator-Implementierung druckt, der alle Elemente im Slice durchläuft (slices.All-Funktionalität).
Zuerst werde ich eine kleine Hilfsfunktion erstellen, um eine Meldung mit der aktuellen Ausführungszeit abzumelden.
import ( "fmt" "time" ) var START time.Time = time.Now() func logt(message string) { fmt.Println(time.Since(START), message) }
Zurück zum Iterator:
import ( "iter" ) func DebugIter[E any](slice []E) iter.Seq2[int, E] { logt("DebugIter called") // the same way iter.All returned function // we called in order to iterate over slice // here we are returning a function to // iterate over all slice elements too return func(yield func(int, E) bool) { logt("Seq2 return function called, starting loop") for index, element := range slice { logt("in loop, calling yield") shouldContinue := yield(index, element) if !shouldContinue { logt("in loop, yield returned false") return } logt("in loop, yield returned true") } } }
Ich habe einige Debug-Druckanweisungen hinzugefügt, damit wir die Ausführungsreihenfolge des Iterators besser sehen können und wie er auf verschiedene Schlüsselwörter wie break und continue reagiert.
Zuletzt verwenden wir den implementierten Iterator:
func main() { slice := []string{ "Element 1", "Element 2", "Element 3", "Element 4", } for index, element := range DebugIter(slice) { message := "got element in range of iter: " + element logt(message) if index >= 2 { break } if index > 0 { continue } time.Sleep(2 * time.Second) logt("ended sleep in range of iter") } }
Gibt uns die Ausgabe:
11.125µs DebugIter called 39.292µs Seq2 return function called, starting loop 42.459µs in loop, calling yield 44.292µs got element in range of iter: Element 1 2.001194292s ended sleep in range of iter 2.001280459s in loop, yield returned true 2.001283917s in loop, calling yield 2.001287042s got element in range of iter: Element 2 2.001291084s in loop, yield returned true 2.001293125s in loop, calling yield 2.0012955s got element in range of iter: Element 3 2.001297542s in loop, yield returned false
Dieses Beispiel zeigt ziemlich gut, wie Iteratoren funktionieren und ausgeführt werden. Bei Verwendung eines Iterators in einer Bereichsschleife werden alle Anweisungen im Schleifenblock gewissermaßen in eine Funktion namens yield „verschoben“. Wenn wir yield aufrufen, bitten wir go runtime im Wesentlichen darum, alles auszuführen, was sich im Schleifenblock mit dem folgenden Wert für diese Iteration befindet. Aus diesem Grund wird yield auch blockiert, wenn der Schleifenkörper blockiert wird. Falls die Laufzeit feststellt, dass diese Schleifeniteration gestoppt werden soll, gibt yield „false“ zurück. Dies kann passieren, wenn das Schlüsselwort „break“ während der Ausführung des Schleifenblocks erfüllt wird. In diesem Fall sollten wir yield nicht mehr aufrufen. Andernfalls sollten wir weiterhin yield aufrufen.
Vollständiger Code:
package main import ( "fmt" "time" "iter" ) var START time.Time = time.Now() func logt(message string) { fmt.Println(time.Since(START), message) } func DebugIter[E any](slice []E) iter.Seq2[int, E] { logt("DebugIter called") // the same way iter.All returned function // we called in order to iterate over slice // here we are returning a function to // iterate over all slice elements too return func(yield func(int, E) bool) { logt("Seq2 return function called, starting loop") for index, element := range slice { logt("in loop, calling yield for") shouldContinue := yield(index, element) if !shouldContinue { logt("in loop, yield returned false") return } logt("in loop, yield returned true") } } } func main() { slice := []string{ "Element 1", "Element 2", "Element 3", "Element 4", } for index, element := range DebugIter(slice) { message := "got element in range of iter: " + element logt(message) if index >= 2 { break } if index > 0 { continue } time.Sleep(2 * time.Second) logt("ended sleep in range of iter") } // unfold compiler magic // DebugIter(slice)(func (index int, element string) bool { // message := "got element in range of iter: " + element // logt(message) // if index >= 2 { // return false // } // if index > 0 { // return true // } // time.Sleep(2 * time.Second) // logt("ended sleep in range of iter") // // return true // }) }
Das obige ist der detaillierte Inhalt vonGo-Teratoren verstehen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!