Skip to content

Iteratoren

In Go wird das Schlüsselwort for range für die Iteration über bestimmte Datenstrukturen verwendet. In den vorherigen Kapiteln wurden einige Anwendungen vorgestellt. Es kann nur auf wenige eingebaute Datenstrukturen angewendet werden:

  • Arrays
  • Slices
  • Strings
  • Maps
  • Channels
  • Ganzzahlwerte

Dies ist nicht sehr flexibel und bietet keine Erweiterbarkeit für benutzerdefinierte Typen. Seit Go 1.23 unterstützt das Schlüsselwort for range jedoch range over func, wodurch benutzerdefinierte Iteratoren möglich werden.

Einführung

Betrachten wir ein Beispiel zur Einführung in Iteratoren. Erinnern Sie sich an das Beispiel der Closure zur Berechnung der Fibonacci-Folge? Die Implementierung war:

go
func Fibonacci(n int) func() (int, bool) {
  a, b, c := 1, 1, 2
  i := 0
  return func() (int, bool) {
    if i >= n {
      return 0, false
    } else if i < 2 {
      f := i
      i++
      return f, true
    }

    a, b = b, c
    c = a + b
    i++

    return a, true
  }
}

Wir können dies in einen Iterator umwandeln:

go
func Fibonacci(n int) func(yield func(int) bool) {
  a, b, c := 0, 1, 1
  return func(yield func(int) bool) {
    for range n {
      if !yield(a) {
        return
      }
      a, b = b, c
      c = a + b
    }
  }
}

Go-Iteratoren sind im range over func-Stil. Wir können direkt das Schlüsselwort for range verwenden:

go
func main() {
    n := 8
  for f := range Fibonacci(n) {
    fmt.Println(f)
  }
}

Ausgabe:

0
1
1
2
3
5
8
13

Wie oben gezeigt, ist ein Iterator eine Closure-Funktion, die eine Callback-Funktion als Parameter akzeptiert. Das yield ist nur eine Callback-Funktion, kein Schlüsselwort.

Push-Iteratoren

Die Definition von Iteratoren finden wir in der iter-Bibliothek:

Ein Iterator ist eine Funktion, die aufeinanderfolgende Elemente einer Sequenz an eine Callback-Funktion übergibt, konventionell yield genannt.

Ein Iterator ist also eine Funktion, die eine Callback-Funktion als Parameter akzeptiert und während der Iteration Elemente an yield übergibt.

Der Typ iter.Seq ist in der Standardbibliothek definiert:

go
type Seq[V any] func(yield func(V) bool)

type Seq2[K, V any] func(yield func(K, V) bool)

Pull-Iteratoren

Pull-Iteratoren sind das Gegenteil von Push-Iteratoren. Der Benutzer kontrolliert die Iterationslogik und aktiviert die Sequenzelemente. Pull-Iteratoren haben typischerweise Funktionen wie next() und stop() zur Steuerung.

go
func Pull[V any](seq Seq[V]) (next func() (V, bool), stop func())

func Pull2[K, V any](seq Seq2[K, V]) (next func() (K, V, bool), stop func())

Beispiel:

go
func main() {
  n := 10
  next, stop := iter.Pull(Fibonacci(n))
  defer stop()
  for {
    fibn, ok := next()
    if !ok {
      break
    }
    fmt.Println(fibn)
  }
}

Fehlerbehandlung

Bei Fehlern während der Iteration können diese an yield übergeben werden:

go
func ScanLines(reader io.Reader) iter.Seq2[string, error] {
  scanner := bufio.NewScanner(reader)
  return func(yield func(string, error) bool) {
    for scanner.Scan() {
      if !yield(scanner.Text(), scanner.Err()) {
        return
      }
    }
  }
}

Standardbibliothek

Viele Standardbibliotheken unterstützen nun Iteratoren:

slices.All

go
func All[Slice ~[]E, E any](s Slice) iter.Seq2[int, E]

slices.Values

go
func Values[Slice ~[]E, E any](s Slice) iter.Seq[E]

slices.Chunk

go
func Chunk[Slice ~[]E, E any](s Slice, n int) iter.Seq[Slice]

maps.Keys

go
func Keys[Map ~map[K]V, K comparable, V any](m Map) iter.Seq[K]

maps.Values

go
func Values[Map ~map[K]V, K comparable, V any](m Map) iter.Seq[V]

Leistung

Da Go viele Optimierungen für Iteratoren durchgeführt hat, ist die Leistung nicht so gut wie bei nativen for range-Schleifen. Push-Iteratoren sind jedoch nicht weit von nativen Schleifen entfernt, während Pull-Iteratoren deutlich langsamer sind.

Zusammenfassung

Ähnlich wie bei Generika sind auch Go-Iteratoren umstritten. Einige argumentieren, dass Iteratoren zu viel Komplexität einführen und der Go-Philosophie der Einfachheit widersprechen. Rational betrachtet ermöglichen Iteratoren jedoch bequemeres Programmieren, insbesondere bei der Arbeit mit Slice-Typen, führen aber auch zu etwas mehr Komplexität. Insgesamt ist dies ein nützliches Feature.

Golang by www.golangdev.cn edit