leanmind logo leanmind text logo

Blog

TDD Avanzado

Las herramientas que se necesitan para aplicar TDD en el mundo real en cualquier proyecto.

Bajando a tierra la programación funcional

Por Aitor Santana

Imagina que estás construyendo una aplicación, y te encuentras con un problema recurrente: el manejo de datos complejos y las operaciones que deseas realizar sobre ellos. La programación funcional es un paradigma que puede ayudarte a abordar este desafío, de manera elegante y eficiente.

Hasta hace muy poco, la programación funcional me parecía un territorio misterioso y complicado debido a su fuerte conexión con las matemáticas. Sin embargo, asistí a una charla de Henar Hernández en autentifront donde explicaba algunos conceptos de programación funcional de manera sencilla. En este artículo, compartiré lo que aprendí.

Fundamentos de la programación funcional

En la programación funcional existen una serie de fundamentos o características importantes que se deben cumplir:

La programación tiene que ser declarativa

En la programación funcional, se enfatiza la programación declarativa sobre la programación imperativa. Esto significa que declaras lo que tu aplicación debe hacer, en lugar de cómo debe hacerlo, encapsulando la lógica en funciones. Veamos un ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Código imperativo
const listOfNumbers = [1,2,3,4,5,6,7,8,9]
let sumResult = 0
for (let i = 0; i < listOfNumbers.length; i++){
  sumResult += listOfNumbers[i]
}

// Código Declarativo
const listOfNumbers = [1,2,3,4,5,6,7,8,9]
const sum = (firstOperant: number, secondOperant: number) => firstOperant + secondOperant
const sumResult = listOfNumbers.reduce(sum)

Las funciones son puras

En la programación funcional, se utilizan funciones puras, que son funciones que sólo dependen de sus argumentos y no tienen efectos secundarios en otras partes de la aplicación. Comparémoslas:

1
2
3
4
5
6
// Impura
let currency = "€"
const calculateImpurePrice = (price: number) => `${price}${currency}`

// Pura
const calculatePurePrice = (price: string, currency: string) =>`${price}${currency}`

Funciones de Alto Orden

Son funciones que reciben otras funciones como parámetros.

1
2
3
4
5
6
7
type AritmethicFunction = (firstOperant: number, secondOperant: number) => number
  
const calculator = (firstOperand: number, secondOperant: number, aricmeticOperation: AritmethicFunction) => (
    aricmeticOperation(firstOperand, secondOperant)
)
const sum = (firstOperant: number, secondOperant: number) => firstOperant + secondOperant
const resultOfSum = calculator(3,4,sum) // 7

Determinismo

En la programación funcional, las funciones son deterministas, lo que significa que, dado un conjunto de argumentos, siempre producirán el mismo resultado:

1
2
3
4
5
6
const sum = (firstOperant: number, secondOperant: number) => firstOperant + secondOperant

console.log(sum(3,4)) // 7
console.log(sum(5,4)) // 9
console.log(sum(3,1)) // 4
console.log(sum(2,2)) // 4

Inmutabilidad

La inmutabilidad es un principio fundamental en este paradigma. Significa que los datos no cambian su estado, son constantes. Esto aporta seguridad y control al código.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const listOfNumbers = [1,2,3,4,5,6,7,8,9]
const anotherListOfNumbers = [1,2,3,4,5,6,7,8,9]

// Mutable, modifica el elemento original
listOfNumbers.reverse()
console.log(listOfNumbers) // [9, 8, 7, 6, 5, 4, 3, 2, 1]

// Inmutable, no modifica el array original
const reverseListOfNumbers = anotherListOfNumbers.toReversed()
console.log(anotherListOfNumbers) // [1,2,3,4,5,6,7,8,9]
console.log(reverseListOfNumbers) // [9, 8, 7, 6, 5, 4, 3, 2, 1]

Cumplir con estos fundamentos nos permite descomponer tareas complejas, hacer que el código y nuestras funciones sean más reutilizables, legibles y, sobre todo, más testeables.

Teoría de Categorías

La programación funcional se basa en la teoría de categorías, y una categoría según Wikipedia es:

En teoría de categorías, una categoría es una estructura algebraica que consta de una colección de objetos, conectados unos con otros mediante flechas tales que se cumplen las siguientes propiedades básicas: las flechas se pueden componer unas con otras de manera asociativa, y para cada objeto existe una flecha que se comporta como un elemento neutro bajo la composición. Fuente: Categoría(matemáticas)

Desglosemos algunos de los conceptos de está definición para entender realmente que significa:

FantasyLand

Fantasyland es un repositorio en el que se encuentra la especificación algebraica de JavaScript, especifica la interoperabilidad de estructuras algebraicas comunes.

En dicha especificación encontraremos información en detalle sobre este tipo de estructuras.

Funtores

Un funtor es un tipo de dato algebraico que actúa como una “caja mágica”, capaz de realizar transformaciones en objetos. En programación, esto se traduce en tener un método “map”, que permite aplicar una operación a cada elemento de una estructura de datos, como una lista, de una manera predecible y coherente. En matemáticas, los funtores son utilizados para relacionar objetos de diferentes categorías de una manera especial. Un ejemplo de funtor pueden ser los arrays de JavaScript. Veamos una implementación de un funtor básico en este lenguaje:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const Functor = <T>(value: T) => ({
  getValue: () => value,
  map: <U>(f: (value: T) => U) => Functor(f(value)),
});

// Ejemplo de uso
const stringFunctor = Functor("Hola, Mundo!");

const lengthFunctor = stringFunctor.map((str) => str.length);

console.log(lengthFunctor.getValue()); // Devuelve la longitud de la cadena: 12

Maybe

Es un tipo de funtor que nos sirve para controlar nulos o undefined. Si el valor no es nulo o undefined aplicamos la función de mapeo, en caso contrario, no tenemos por qué aplicarla.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const Maybe = <T>(value: T | null) => ({
    map: <U>(f: (value: T) => U) => Maybe<U>(value ? f(value) : null),
    flatMap: <U>(f: (value: T) => Maybe<U>) => Maybe<U>(value !== null ? f(value) : null),
    getValue: () => value,
});

// Ejemplo de uso:
const numberMaybe = Maybe<number>(42);
const squaredMaybe = numberMaybe.map((x) => x * x);

console.log(squaredMaybe.getValue()); // 1764

// Ejemplo de uso con valor nulo
const nullMaybe = Maybe<number>(null);
const result = nullMaybe.map((x) => x * 2);

console.log(result.getValue()); // null


// Ejemplo de uso con flatMap
const user = Maybe({
    name: 'John',
    address: Maybe({
        city: 'New York',
        postalCode: '10001'
    })
});

const postalCode = user
    .flatMap((u) => u.address)
    .flatMap((a) => a.postalCode)
    .getValue();

if (postalCode !== null) {
    console.log(`El código postal es: ${postalCode}`);
} else {
    console.log('No se encontró el código postal.');
}

Es importante mencionar que Maybe es un ejemplo de mónada, un subtipo de funtor. Es por ello que requiere el método flatMap/bind, para cumplir la propiedad de la unión, pero profundizar en este concepto va más allá del alcance de este artículo.

Conclusión

En este artículo, hemos explorado los fundamentos de la programación funcional, desde la programación declarativa hasta los funtores y el tipo Maybe.

La programación funcional ofrece una forma elegante y eficiente de abordar desafíos de desarrollo de software. Con una base sólida en estos conceptos, puedes escribir código más legible y robusto. ¡Anímate a explorar más la programación funcional y aplicar estos principios en tu trabajo!

Publicado el 18/10/2023 por

¿Quieres más? te invitamos a suscribirte a nuestro boletín para avisarte cada vez que recopilemos contenido de calidad que compartir.

Si disfrutas leyendo nuestro blog, ¿imaginas lo divertido que sería trabajar con nosotros? ¿te gustaría?

Impulsamos el crecimiento profesional de tu equipo de developers