Por qué la Programación Funcional en Go es el Estilo de Código Definitivo

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?