Programación funcional en Go: de la teoría a la práctica
Go (Golang) no fue diseñado originalmente como un lenguaje puramente funcional. Sin embargo, su potente sistema de tipos, el soporte para funciones de primera clase y los closures permiten aplicar con éxito los principios de la programación funcional (FP). En este artículo, analizaremos cómo usar el estilo funcional en Go para escribir código más predecible, comprobable y seguro para la concurrencia.
1. Funciones de primera clase y funciones de orden superior
En Go, las funciones son objetos de primera clase. Esto significa que se pueden asignar a variables, pasar como argumentos y devolver desde otras funciones. Las funciones que trabajan con otras funciones se denominan funciones de orden superior.
Ejemplo: pasar una función como argumento
package main
import "fmt"
// Función de orden superior: toma una función y la aplica a un númerofunc applyOperation(x int, op func(int) int) int { return op(x)}
func double(x int) int { return x * 2}
func square(x int) int { return x * x}
func main() { result1 := applyOperation(5, double) result2 := applyOperation(5, square)
fmt.Println("Double:", result1) // 10 fmt.Println("Square:", result2) // 25}Ejemplo: devolver una función desde una función (fábrica de funciones)
package main
import "fmt"
// Devuelve una función que suma un valor dadofunc createAdder(x int) func(int) int { return func(y int) int { return x + y }}
func main() { addFive := createAdder(5) addTen := createAdder(10)
fmt.Println(addFive(3)) // 8 fmt.Println(addTen(3)) // 13}2. Closures en Go
Un closure es una función que "recuerda" las variables del ámbito externo, incluso después de que ese ámbito haya terminado su ejecución. Los closures son una herramienta poderosa para crear estado encapsulado y fábricas de funciones.
Ejemplo: generador de secuencias
package main
import "fmt"
func sequenceGenerator() func() int { i := 0 return func() int { i++ return i }}
func main() { next := sequenceGenerator()
fmt.Println(next()) // 1 fmt.Println(next()) // 2 fmt.Println(next()) // 3
// Nuevo generador comienza en 1 next2 := sequenceGenerator() fmt.Println(next2()) // 1}Importante: Tenga cuidado con los closures en bucles. En versiones antiguas de Go (anteriores a la 1.22), la variable i en el bucle se reutilizaba, lo que provocaba errores. A partir de Go 1.22, las variables del bucle se capturan por valor.
3. Funciones puras e inmutabilidad
Una función pura es una función que:
- Siempre devuelve el mismo resultado para los mismos argumentos.
- No tiene efectos secundarios (no modifica el estado externo, no escribe en archivos/red).
Go no tiene soporte incorporado para la inmutabilidad, pero podemos seguir este principio por nuestra cuenta.
Ejemplo: función pura vs función impura
package main
import "fmt"
// Función IMPURA: modifica un slice externovar counter int
func impureAddToSlice(s []int, val int) []int { counter++ // efecto secundario return append(s, val)}
// Función PURA: crea un nuevo slicefunc pureAddToSlice(s []int, val int) []int { newSlice := make([]int, len(s)+1) copy(newSlice, s) newSlice[len(s)] = val return newSlice}
func main() { original := []int{1, 2, 3}
// Enfoque puro result := pureAddToSlice(original, 4) fmt.Println("Original:", original) // [1 2 3] - no cambió fmt.Println("Result:", result) // [1 2 3 4]}Consejo: Para trabajar con estructuras inmutables