Maison >développement back-end >Golang >Comprendre les térateurs Go

Comprendre les térateurs Go

王林
王林original
2024-08-18 06:32:061059parcourir

Understanding Go terators

Beaucoup de gens semblent être confus par les itérateurs nouvellement ajoutés dans Go, c'est pourquoi j'ai décidé d'écrire un autre article pour tenter de les expliquer de la manière la plus simple possible.

Comment sont-ils appelés par Go ?

Tout d'abord, je pense qu'il est important de comprendre comment les itérateurs sont même appelés et utilisés par Go, et c'est en fait assez simple, utilisons les slices.All itérateur comme exemple. Voici comment vous utiliseriez normalement cet itérateur :

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
}

Et voici à quoi cela ressemble réellement :

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
}

Ce qui se passe, c'est que le corps de la boucle est "déplacé" pour produire une fonction qui est transmise à l'itérateur, tandis que continue et break sont transformés pour renvoyer respectivement true et false. return true est également ajouté à la fin de la boucle pour signaler que nous aimerions obtenir l'élément suivant, si rien d'autre n'a pris une autre décision auparavant.

Ce n'est pas exactement le déroulement de ce que fait le compilateur et je n'ai pas vérifié l'implémentation de Go pour vérifier cela, mais ils produisent des résultats équivalents à partir de mes observations.

Comment créer votre propre itérateur et son exécution

Maintenant que vous comprenez comment ils sont appelés et que vous réalisez à quel point c'est simple, il sera beaucoup plus facile de comprendre comment créer votre propre itérateur et son exécution.

Créons un itérateur de débogage qui imprimera des messages de débogage pour chaque étape de la mise en œuvre de l'itérateur qui parcourra tous les éléments de la tranche (fonctionnalités slices.All).

Tout d'abord, je vais créer une petite fonction d'assistance pour déconnecter le message avec l'heure d'exécution actuelle.

import (
    "fmt"
    "time"
)

var START time.Time = time.Now()

func logt(message string) {
    fmt.Println(time.Since(START), message)
}

Retour à l'itérateur :

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")
        }
    }
}

J'ai ajouté quelques instructions d'impression de débogage afin que nous puissions mieux voir l'ordre d'exécution de l'itérateur et comment il réagira à différents mots-clés comme break et continue.

Enfin, utilisons l'itérateur implémenté :

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")
    }
}

Nous donnera le résultat :

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

Cet exemple montre assez bien comment fonctionnent et sont exécutés les itérateurs. Lors de l'utilisation d'un itérateur dans une boucle de plage, toutes les instructions du bloc de boucle sont en quelque sorte "déplacées" vers une fonction appelée rendement. Lorsque nous appelons rendement, nous demandons essentiellement à go runtime d'exécuter tout ce qui se trouve dans le bloc de boucle avec la valeur suivante pour cette itération, c'est aussi pourquoi rendement sera bloqué, si le corps de la boucle est bloqué. Dans le cas où l'exécution détermine que cette itération de boucle est censée s'arrêter, rendement retournera faux, cela peut se produire lorsque le mot-clé break est rencontré lors de l'exécution du bloc de boucle, nous ne devrions plus appeler rendement si cela se produit. Sinon, nous devrions continuer à appeler rendement.

Code complet :

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
    //  })
}

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn