Programación funcional en Go: de la A a la Z
Go (Golang) es un lenguaje que a menudo se asocia con la programación imperativa y concurrente. Sin embargo, su sistema de tipos flexible y las funciones de primera clase permiten aplicar con éxito el estilo funcional. En este artículo, analizaremos cómo usar la programación funcional (FP) en Go: desde conceptos básicos hasta ejemplos prácticos. Aprenderá a escribir código más limpio, predecible y comprobable.
¿Qué es la programación funcional en el contexto de Go?
La programación funcional es un paradigma donde los cálculos se construyen mediante la aplicación de funciones, evitando cambios de estado y efectos secundarios. En Go, esto se expresa a través de:
- Funciones puras — el resultado depende solo de los argumentos, sin mutación de datos externos.
- Funciones de orden superior — funciones que toman otras funciones como argumentos o las devuelven.
- Clausuras — funciones anónimas que "recuerdan" variables del ámbito externo.
- Inmutabilidad de datos — preferencia por copiar en lugar de modificar.
Aunque Go no es un lenguaje puramente funcional (no tiene mónadas o tipos de datos algebraicos), proporciona suficientes herramientas para usar eficazmente los enfoques de FP.
Funciones anónimas y clausuras
En Go, las funciones son objetos de primera clase. Se pueden asignar a variables, pasar a otras funciones y devolver. Las funciones anónimas (lambdas) son la base del estilo funcional.
Ejemplo de función anónima
package main
import "fmt"
func main() { // Función anónima asignada a una variable multiply := func(x, y int) int { return x * y }
result := multiply(5, 3) fmt.Println(result) // 15}Clausuras (Closures)
Una clausura es una función anónima que "captura" variables del ámbito externo. Es una herramienta poderosa para crear generadores y funciones con estado.
package main
import "fmt"
// Generador de secuenciafunc sequence() func() int { i := 0 return func() int { i++ return i }}
func main() { next := sequence() fmt.Println(next()) // 1 fmt.Println(next()) // 2 fmt.Println(next()) // 3
// Cada llamada a sequence() crea una nueva clausura next2 := sequence() fmt.Println(next2()) // 1}Las clausuras son ideales para encapsular estado sin usar variables globales; esta es la base del enfoque funcional en Go.
Funciones puras e inmutabilidad
Una función pura no tiene efectos secundarios y no modifica los datos pasados. En Go, esto requiere un enfoque consciente, ya que los slices y mapas se pasan por referencia.
Ejemplo de función pura (sin mutación)
package main
import "fmt"
// Función pura: no modifica el slice originalfunc doubleValues(input []int) []int { result := make([]int, len(input)) for i, v := range input { result[i] = v * 2 } return result}
func main() { original := []int{1, 2, 3, 4} doubled := doubleValues(original)
fmt.Println("Original:", original) // [1 2 3 4] fmt.Println("Doubled:", doubled) // [2 4 6 8]}Observe: creamos un nuevo slice en lugar de modificar el original. Esto garantiza la inmutabilidad (immutability), un principio clave de FP.
Funciones de orden superior: map, filter, reduce
Aunque Go no tiene métodos integrados map/filter/reduce para slices, es fácil implementarlos por su cuenta.
package main
import "fmt"
// Map — aplica una función a cada elementofunc Map[T any](items []T, fn func(T) T) []T { result := make([]T, len(items)) for