"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Entendiendo los teradores de Go

Entendiendo los teradores de Go

Publicado el 2024-08-21
Navegar:606

Understanding Go terators

Muchas personas parecen estar confundidas por los iteradores recién agregados en Go, es por eso que decidí escribir otro artículo intentando explicarlos de la manera más simple posible.

¿Cómo los llama Go?

Primero, creo que es importante comprender cómo Go llama y utiliza los iteradores, y en realidad es bastante simple, usemos cortes. Todo iterador como ejemplo. Así es como normalmente usarías este iterador:

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
}

Y así es como se ve realmente:

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
}

Lo que sucede es que el cuerpo del bucle se "mueve" para producir la función que se pasa al iterador, mientras que continuar y romper se transforman para devolver verdadero y falso respectivamente. return true también se agrega al final del ciclo para indicar que nos gustaría obtener el siguiente elemento, si nada más tomó otra decisión antes.

Esto no es un desarrollo exacto de lo que está haciendo el compilador y no he verificado la implementación de Go para verificar esto, pero producen resultados equivalentes a partir de mis observaciones.

Cómo crear tu propio iterador y su ejecución.

Ahora que entiendes cómo se llaman y te das cuenta de lo simple que es en realidad, será mucho más fácil entender cómo crear tu propio iterador y su ejecución.

Creemos un iterador de depuración que imprimirá mensajes de depuración para cada paso de la implementación del iterador que recorrerá todos los elementos en el segmento (rebanados. Todas las funciones).

Primero, crearé una pequeña función auxiliar para cerrar sesión en el mensaje con el tiempo de ejecución actual.

import (
    "fmt"
    "time"
)

var START time.Time = time.Now()

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

Volver al iterador:

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

Agregué algunas declaraciones de impresión de depuración para que podamos ver mejor el orden de ejecución del iterador y cómo reaccionará ante diferentes palabras clave como interrumpir y continuar.

Finalmente, usemos el iterador implementado:

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

Nos dará el resultado:

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

Este ejemplo muestra bastante bien cómo funcionan y se ejecutan los iteradores. Cuando se usa un iterador en un bucle de rango, todas las instrucciones en el bloque de bucle se "mueven" a una función que se llama rendimiento. Cuando llamamos a rendimiento, esencialmente le pedimos a go runtime que ejecute lo que esté ubicado en el bloque de bucle con el siguiente valor para esta iteración, esa es también la razón por la que se bloqueará rendimiento, si el cuerpo del bucle se bloquea. En caso de que el tiempo de ejecución determine que se supone que esta iteración del bucle debe detenerse, el rendimiento devolverá falso, puede suceder cuando se cumple la palabra clave break durante la ejecución del bloque del bucle, ya no deberíamos llamar a rendimiento si eso sucede. De lo contrario, deberíamos seguir llamando al rendimiento.

Código completo:

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
    //  })
}
Declaración de liberación Este artículo se reproduce en: https://dev.to/wmdanor/understanding-go-123-iterators-3ai1?1 Si hay alguna infracción, comuníquese con [email protected] para eliminarla.
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3