funciones-anonimas-closures-go

Funciones anónimas y closures en Go: estado y callbacks

  • 4 min

Una función anónima en Go es una función sin nombre que podemos guardar, pasar o ejecutar al vuelo. Un closure aparece cuando esa función recuerda variables del contexto donde fue creada.

En el artículo anterior vimos cómo declarar funciones con nombre. En Go, además, las funciones son valores de primera clase.

Esto significa que podemos manejar una función como un valor, igual que hacemos con un int o un string.

  • Puedes guardar una función en una variable.
  • Puedes pasar una función como argumento a otra.
  • Puedes devolver una función desde otra función.

De esta capacidad nacen las funciones anónimas y los closures.

Funciones anónimas

Una función anónima es, literalmente, una función que no tiene nombre. Se define en el momento en que se necesita.

La sintaxis es idéntica a una función normal, pero omitiendo el identificador después de func.

package main

import "fmt"

func main() {
    // 1. Guardamos la función en una variable 'saludo'
    saludo := func(nombre string) {
        fmt.Printf("Hola %s, soy anónima\n", nombre)
    }

    // 2. Usamos la variable como si fuera una función
    saludo("Luis")

    fmt.Printf("El tipo de 'saludo' es: %T\n", saludo)
}
Copied!

Ejecución inmediata (IIFE)

A veces queremos declarar una función y ejecutarla en el mismo instante, sin guardarla en ninguna variable. Esto es útil para aislar variables en un scope pequeño.

func main() {
    func() {
        fmt.Println("Me ejecuto ahora mismo y desaparezco")
    }() // Los paréntesis finales () ejecutan la función
}
Copied!

Funciones como argumentos

Como las funciones son valores, podemos pasarlas a otras funciones. Esto permite escribir código flexible y reutilizable, como filtros, transformaciones o handlers web.

La siguiente función opera sobre dos números, mientras que la operación concreta llega como argumento.

// Esta función recibe dos enteros y UNA FUNCIÓN que define la operación
func Operar(a, b int, accion func(int, int) int) int {
    return accion(a, b)
}

func main() {
    suma := func(x, y int) int { return x + y }
    resta := func(x, y int) int { return x - y }

    fmt.Println(Operar(10, 5, suma))  // 15
    fmt.Println(Operar(10, 5, resta)) // 5
}
Copied!

Closures: funciones que capturan su entorno

En este punto la cosa se pone interesante.

Un Closure es una función anónima que accede a variables definidas fuera de su propio cuerpo.

Lo interesante es que la función anónima “captura” o “encierra” (closes over) esas variables. Aunque la función padre termine y sus variables locales debieran destruirse, el closure las mantiene vivas mientras él exista.

Un generador de secuencias

Vamos a crear una función que devuelve otra función. La función devuelta es un contador que “recuerda” por qué número iba.

package main

import "fmt"

// Devuelve una función que devuelve un int: func() int
func crearSecuencia() func() int {
    i := 0 // Variable local de crearSecuencia

    // Devolvemos una función anónima (Closure)
    return func() int {
        i++ // ¡Esta función usa 'i' de fuera!
        return i
    }
}

func main() {
    // Creamos un generador
    siguiente := crearSecuencia()

    fmt.Println(siguiente()) // 1
    fmt.Println(siguiente()) // 2
    fmt.Println(siguiente()) // 3

    // Creamos OTRO generador independiente
    otro := crearSecuencia()
    fmt.Println(otro())      // 1 (Tiene su propia variable 'i')
}
Copied!

¿Qué ha pasado aquí?

  1. Llamamos a crearSecuencia(). Se crea la variable local i = 0.
  2. Devolvemos la función anónima, que conserva acceso a esa variable.
  3. El compilador garantiza que i permanezca viva mientras el closure la necesite. Su análisis de escape decidirá si debe alojarla en el heap; no todos los closures implican una asignación allí.
  4. La variable siguiente contiene una función asociada a esa instancia concreta de i.

Los Closures son fundamentales para mantener estado sin usar variables globales. Cada vez que llamas a la función creadora (crearSecuencia), se crea un nuevo “entorno” con sus propias variables.

Aplicación práctica: middleware

En el desarrollo web con Go, los closures se usan constantemente para crear Middlewares.

Podemos envolver una función para medir cuánto tarda sin modificar la función original.

func medirTiempo(accion func()) func() {
    return func() {
        start := time.Now()

        accion() // Ejecutamos la original

        fmt.Println("Ha tardado:", time.Since(start))
    }
}
Copied!