Funciones All y Collect

En Go 1.23 se incorporan a la biblioteca estándar nuevas funciones para convertir de slices a iteradores (slices.All, slices.Values y slices.Backwards), mapas a iteradores (maps.All), iteradores a slices (slices.Collect) e iteradores a mapas (slices.Maps). Aquí te muestro cómo las puedes usar para convertir entre una cosa y la otra.

La versión 1.23 de Go ha introducido funciones nuevas en su biblioteca estándar que facilitan la interoperabilidad entre los tipos tradicionales como slices y mapas y las nuevas funciones iteradoras del lenguaje. Estas funciones nos permiten convertir fácilmente entre slices, mapas e iteradores, lo que mejora la flexibilidad y la forma en que podemos manejar colecciones en Go.

Para empezar, tenemos la función slices.all. Esta función recibe un slice y devuelve una secuencia iteradora de pares, donde cada par contiene la posición del elemento y el propio valor. Esto es muy parecido a cómo funciona el range tradicional en Go, que al recorrer un slice nos da tanto el índice como el valor. Por ejemplo, si tenemos un slice de cadenas como ["lunes", "martes", "miércoles"], al usar slices.all obtendremos un iterador que nos permite acceder a cada posición junto con su valor correspondiente.

Esta función es especialmente útil cuando queremos trabajar con iteradores en lugar de los tipos tradicionales, ya que podemos recorrerlos con un range o invocarlos con un callback que reciba la posición y el valor. Así, podemos imprimir o procesar cada elemento junto con su índice, manteniendo la estructura que ya conocemos.

Sin embargo, si no nos interesa la posición y solo queremos los valores, Go también nos ofrece la función slices.values. Esta función toma un slice y devuelve una secuencia iteradora que solo contiene los valores, sin las posiciones. Esto simplifica el recorrido cuando solo necesitamos los elementos y no su índice.

En cuanto a los mapas, el proceso es similar. La función maps.all convierte un mapa en un iterador de pares clave-valor. Esto tiene sentido porque un mapa está formado por pares, y al usar esta función podemos recorrer el mapa de forma iteradora, accediendo a cada clave y su valor asociado. Así, podemos procesar mapas con las nuevas funciones iteradoras sin perder la estructura clave-valor.

Además, estas conversiones funcionan en ambos sentidos. Podemos convertir iteradores en slices o mapas usando las funciones slices.collect y maps.collect. Por ejemplo, si tenemos un iterador que genera una secuencia, como una sucesión de Fibonacci, podemos usar slices.collect para recolectar todos los elementos en un slice tradicional. Esto es útil cuando queremos manipular o devolver los datos en formatos convencionales, como en una API.

De forma similar, maps.collect acepta un iterador de pares clave-valor y devuelve un mapa. Es importante tener en cuenta que si el iterador genera claves duplicadas, el mapa resultante conservará el valor de la última aparición de cada clave, siguiendo la semántica habitual de los mapas en Go.

Para ilustrar esto, podemos crear un iterador que genere pares donde la clave sea la posición y el valor sea el número de Fibonacci correspondiente. Al pasar este iterador a maps.collect, obtenemos un mapa que asocia cada índice con su valor de Fibonacci.

En resumen, estas funciones nos permiten traducir entre slices, mapas e iteradores de forma sencilla y eficiente. Con slices.all y maps.all convertimos slices y mapas en iteradores, y con slices.collect y maps.collect hacemos el camino inverso. Esta característica es una de las novedades más relevantes de Go 1.23, que amplía las posibilidades para trabajar con colecciones y secuencias en el lenguaje.

A continuación, un ejemplo de cómo usar slices.all para convertir un slice en un iterador y recorrerlo con un callback:

package main

import (
    "fmt"
    "golang.org/x/exp/slices"
)

func main() {
    dias := []string{"lunes", "martes", "miércoles"}

    iter := slices.All(dias)

    iter(func(i int, v string) bool {
        fmt.Printf("Posición: %d, Valor: %s\n", i, v)
        return true
    })
}

Y otro ejemplo usando slices.collect para convertir un iterador en un slice, partiendo de una función que genera números de Fibonacci:

package main

import (
    "fmt"
    "golang.org/x/exp/slices"
)

// Fibonacci genera un iterador de números de Fibonacci hasta n elementos
func Fibonacci(n int) func(func(int) bool) {
    a, b := 0, 1
    count := 0
    return func(f func(int) bool) {
        for count < n {
            if !f(a) {
                break
            }
            a, b = b, a+b
            count++
        }
    }
}

func main() {
    iter := Fibonacci(15)
    fibSlice := slices.Collect(iter)
    fmt.Println(fibSlice)
}

Finalmente, un ejemplo con mapas usando maps.all y maps.collect para convertir entre mapas e iteradores:

package main

import (
    "fmt"
    "golang.org/x/exp/maps"
)

func main() {
    m := map[string]int{"a": 1, "b": 2, "c": 3}

    iter := maps.All(m)

    iter(func(k string, v int) bool {
        fmt.Printf("Clave: %s, Valor: %d\n", k, v)
        return true
    })

    // Supongamos que tenemos un iterador de pares clave-valor
    // Aquí lo simulamos con maps.All y luego recolectamos de nuevo en un mapa
    newMap := maps.Collect(iter)
    fmt.Println(newMap)
}

Estas funciones amplían las herramientas que tenemos para trabajar con colecciones en Go, haciendo que la conversión entre diferentes estructuras sea más natural y sencilla.

Lista de reproducción
  1. 1
    Funciones iteradoras
    10 minutos
  2. 2
    Funciones All y Collect
    8 minutos
  3. 3
    Cómo usar pull-iterators
    7 minutos
  4. 4
    Interning en Go con unique.Make
    8 minutos