Cuando piensas en programación funcional, seguramente te vienen a la mente lenguajes como Haskell (con sus funciones puras y mónadas) o JavaScript (con sus funciones de orden superior y callbacks).
Pero, ¿Sabías que también puedes aplicar programación funcional en Go? Aunque no sea lo más común, Go permite escribir código funcional de manera eficiente y elegante.
A continuación, exploraremos conceptos clave de la programación funcional en Go con ejemplos prácticos.
Funciones de Orden Superior
Las funciones de orden superior son aquellas que pueden recibir otras funciones como parámetros o devolverlas como resultado.
Ejemplo: Filtrar números pares
package main
import (
"fmt"
)
func filter(numbers []int, f func(int) bool) []int {
var result []int
for _, value := range numbers {
if f(value) {
result = append(result, value)
}
}
return result
}
func isEven(n int) bool {
return n%2 == 0
}
func main() {
numbers := []int{1, 2, 3, 4}
even := filter(numbers, isEven)
fmt.Println(even) // [2, 4]
}
Aquí, la función filter recibe un slice de enteros y una función que evalúa una condición (isEven). El resultado es una lista con los elementos que cumplen esa condición.
⚡ Currificación (Currying)
La currificación consiste en transformar una función de múltiples argumentos en una serie de funciones, cada una de las cuales recibe un solo argumento.
Ejemplo: Función para sumar números
package main
import "fmt"
func add(a int) func(int) int {
return func(b int) int {
return a + b
}
}
func main() {
addFive := add(5)
fmt.Println(addFive(3)) // 8
}
Aquí, add(5) devuelve una función que suma 5 a cualquier número que reciba.
Inmutabilidad
Uno de los principios fundamentales de la programación funcional es la inmutabilidad: una vez que se crea un objeto, no se modifica. En lugar de alterar los datos originales, se crean nuevas estructuras de datos.
Ejemplo: Clonar y modificar un mapa
package main
import "fmt"
func main() {
obj := map[string]int{"a": 1, "b": 2}
newObj := make(map[string]int)
for k, v := range obj {
newObj[k] = v
}
newObj["b"] = 3
fmt.Println(newObj) // map[a:1 b:3]
}
Aquí, en lugar de modificar obj, creamos una copia newObj y la editamos.
Funciones Puras
Las funciones puras son aquellas que no modifican estados globales y siempre devuelven el mismo resultado para los mismos parámetros.
Ejemplo: Función pura para elevar un número al cuadrado
package main
import "fmt"
func square(x int) int {
return x * x
}
func main() {
fmt.Println(square(5)) // 25
}
La función square no depende de variables externas y siempre devuelve el mismo resultado.
Functores
Un functor es una estructura que puede aplicar una función a sus elementos y devolver una nueva estructura con los resultados.
Ejemplo: Mapear una lista de números
package main
import "fmt"
func mapInts(values []int, f func(int) int) []int {
result := make([]int, len(values))
for i, v := range values {
result[i] = f(v)
}
return result
}
func main() {
numbers := []int{1, 2, 3, 4}
squared := mapInts(numbers, func(x int) int { return x * x })
fmt.Println(squared) // [1, 4, 9, 16]
}
Aplicamos una función (x * x) a cada elemento de la lista y obtenemos un nuevo slice con los resultados.
Monoides
Un monoide es una estructura que..
✅ Tiene una operación asociativa (puede combinarse con otros valores de la misma estructura).
✅ Tiene un elemento neutro que no altera el resultado.
Ejemplo: Suma de números
package main
import "fmt"
func add(a, b int) int {
return a + b
}
func main() {
fmt.Println(add(5, 5)) // 10
fmt.Println(add(5, 0)) // 5
fmt.Println(add(0, 0)) // 0
}
El 0 es el elemento neutro en la suma, porque no altera el resultado.
Mónadas
Las mónadas son una forma de encadenar funciones que trabajan con valores dentro de un contexto (como errores o listas).
Ejemplo: Mónada para manejar errores en Go
package main
import (
"errors"
"fmt"
)
func Maybe(value int, err error, f func(int) (int, error)) (int, error) {
if err != nil {
return 0, err
}
return f(value)
}
func main() {
process := func(v int) (int, error) {
if v < 0 {
return 0, errors.New("valor negativo")
}
return v * v, nil
}
result, err := Maybe(5, nil, process)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Éxito:", result) // Éxito: 25
}
}
Esta “mónada” nos ayuda a manejar errores sin romper el flujo del programa.
Conclusión
Aunque Go no es un lenguaje funcional puro, sí permite aplicar principios de programación funcional para escribir código más limpio, reutilizable y mantenible. ¿Te animas a usar más programación funcional en Go?