Programación funcional en Go: técnicas avanzadas y ejemplos
Go (Golang) no fue diseñado originalmente como un lenguaje puramente funcional, sin embargo, sus potentes capacidades, como los closures, las funciones de primera clase y las gorutinas, permiten aplicar con éxito el paradigma de la programación funcional (PF) a un nivel avanzado. Este artículo está destinado a desarrolladores que ya están familiarizados con los conceptos básicos de Go y desean profundizar en técnicas funcionales para escribir código más predecible, comprobable y concurrente. Exploraremos el currying, la composición de funciones, el trabajo con estructuras similares a mónadas y la integración de PF con gorutinas.
1. Currying y aplicación parcial de funciones
El currying es la transformación de una función que toma varios argumentos en una secuencia de funciones, cada una de las cuales toma un solo argumento. En Go, donde no hay soporte incorporado para currying, lo implementamos a través de closures.
Ejemplo: currying para un logger configurable
package main
import "fmt"
// Función curryficada: prefijo -> mensaje -> salidafunc createLogger(prefix string) func(string) { return func(message string) { fmt.Printf("[%s] %s", prefix, message) }}
func main() { infoLogger := createLogger("INFO") errorLogger := createLogger("ERROR")
infoLogger("Sistema iniciado") errorLogger("Error de conexión") // Salida: // [INFO] Sistema iniciado // [ERROR] Error de conexión}La aplicación parcial es un caso especial de currying, cuando fijamos una parte de los argumentos. En Go, esto es especialmente útil para crear versiones especializadas de funciones.
// Aplicación parcial: fijamos el coeficientefunc multiply(a, b int) int { return a * b}
func partialMultiply(a int) func(int) int { return func(b int) int { return multiply(a, b) }}
func main() { double := partialMultiply(2) triple := partialMultiply(3)
fmt.Println(double(5)) // 10 fmt.Println(triple(5)) // 15}2. Composición de funciones y pipelines
La composición es la combinación de funciones simples en otras más complejas. En Go, podemos construir cadenas de transformaciones, pasando el resultado de una función como entrada a otra. Esto funciona de manera especialmente potente en combinación con interfaces y genéricos (Go 1.18+).
Ejemplo: composición para procesamiento de cadenas
package main
import ( "fmt" "strings" "unicode")
type StringTransform func(string) string
// Compose crea la composición de dos funcionesfunc Compose(f, g StringTransform) StringTransform { return func(s string) string { return f(g(s)) }}
// Pipeline combina varias funcionesfunc Pipeline(transforms ...StringTransform) StringTransform { return func(s string) string { for _, t := range transforms { s = t(s) } return s }}
func removeSpaces(s string) string { return strings.ReplaceAll(s, " ", "")}
func toUpper(s string) string { return strings.ToUpper(s)}
func removeDigits(s string) string { var result strings.Builder for _, r := range s { if !unicode.IsDigit(r) { result.WriteRune(r) } } return result.String()}
func main() { // Composición de dos funciones cleanAndUpper := Compose(removeSpaces, toUpper) result1 := cleanAndUpper("go 1.18 rocks") fmt.Println(result1) // "GO1.18ROCKS"
// Pipeline de tres funciones pipeline := Pipeline(removeSpaces, removeDigits, toUpper) result2 := pipeline("go 1.18 rocks") fmt.Println(result2) // "GOROCKS"}Este enfoque permite construir transformaciones complejas a partir de bloques simples y comprobables. Cada función del pipeline puede ser probada por separado.