Programación funcional en Go: principios y ejemplos

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Programación funcional en Go: del imperativo a la declaratividad



Go (Golang) tradicionalmente se asocia con la programación imperativa y concurrente, sin embargo, su potente sistema de tipos y el soporte para funciones de primera clase hacen posible aplicar muchos principios de la programación funcional (PF). En este artículo, analizaremos cómo aplicar modismos de PF en Go sin sacrificar el rendimiento ni la legibilidad del código.



Principios fundamentales de PF en el contexto de Go



La programación funcional en Go no significa abandonar por completo el estado mutable. En su lugar, utilizamos los siguientes conceptos:



  • Funciones puras — funciones cuyo resultado depende únicamente de los argumentos, sin efectos secundarios.
  • Inmutabilidad (Immutability) — preferencia por crear nuevas estructuras de datos en lugar de modificar las existentes.
  • Funciones de orden superior — funciones que toman otras funciones como argumentos o las devuelven.
  • Clausuras (Closures) — funciones que capturan variables del ámbito externo.


Funciones de orden superior: map, filter, reduce



En Go no hay genéricos incorporados (hasta la versión 1.18), por lo que los clásicos map, filter, reduce se implementan mediante interfaces o copia explícita. Veamos un ejemplo con números enteros:



package main

import "fmt"

// Map — aplica una función a cada elemento del slicefunc Map(s []int, fn func(int) int) []int { result := make([]int, len(s)) for i, v := range s { result[i] = fn(v) } return result}

// Filter — conserva solo los elementos que cumplen la condiciónfunc Filter(s []int, fn func(int) bool) []int { var result []int for _, v := range s { if fn(v) { result = append(result, v) } } return result}

// Reduce — reduce el slice a un solo valorfunc Reduce(s []int, init int, fn func(int, int) int) int { acc := init for _, v := range s { acc = fn(acc, v) } return acc}

func main() { nums := []int{1, 2, 3, 4, 5}

// Duplicamos cada elemento doubled := Map(nums, func(x int) int { return x * 2 }) fmt.Println("Map:", doubled) // [2 4 6 8 10]

// Conservamos solo los pares evens := Filter(nums, func(x int) bool { return x%2 == 0 }) fmt.Println("Filter:", evens) // [2 4]

// Sumamos todos los elementos sum := Reduce(nums, 0, func(acc, x int) int { return acc + x }) fmt.Println("Reduce:", sum) // 15}


Importante: En Go 1.18+ con genéricos se pueden crear versiones universales:



// Generic Mapfunc MapGeneric[T, U any](s []T, fn func(T) U) []U {    result := make([]U, len(s))    for i, v := range s {        result[i] = fn(v)    }    return result}


Clausuras y currying en Go



Las clausuras en Go son una herramienta poderosa para crear funciones que mantienen estado. Veamos un ejemplo de un generador de secuencias:



package main

import "fmt"

// Generador de la secuencia de Fibonaccifunc fibonacci() func() int { a, b := 0, 1 return func() int { a, b = b, a+b return a }}

// Currying: función que toma argumentos por etapasfunc add(a int) func(int) int { return func(b int) int { return a + b }}

func main() { fib := fibonacci() fmt.Println(fib()) // 1 fmt.Println(fib()) // 1 fmt.Println(fib()) // 2

// Currying add5 := add(5) fmt.Println(add5(10)) // 15 fmt.Println(add5(20)) // 25}


Las clausuras son especialmente útiles para:

  • Crear funciones configurables (middleware)
  • Implementar el patrón "fábrica de funciones"
  • Organizar mecanismos de callback


Inmutabilidad

Blogs

Book Recommendations