Después de mucho tiempo aprendiendo y trabajando con programación orientada a objetos, di un paso atrás para pensar en la complejidad del sistema.
"Complexity is anything that makes software hard to understand or to modify.
" - John Outerhout
Investigando un poco, encontré conceptos de programación funcional como inmutabilidad y función pura. Esos conceptos son grandes ventajas para crear funciones sin efectos secundarios, por lo que es más fácil mantener los sistemas, con algunos otros beneficios.
En esta publicación, te contaré más sobre la programación funcional y algunos conceptos importantes, con muchos ejemplos de código.
Este artículo utiliza Clojure como ejemplo de lenguaje de programación para explicar la programación funcional. Si no se siente cómodo con un tipo de lenguaje LISP, también publiqué la misma publicación en JavaScript. Eche un vistazo: Principios de programación funcional en Javascript
¿Qué es la programación funcional?
La programación funcional es un paradigma de programación, un estilo de construcción de la estructura y los elementos de los programas de computadora, que trata la computación como la evaluación de funciones matemáticas y evita el estado cambiante y los datos mutables - WikipediaFunciones puras

El primer concepto fundamental que aprendemos cuando queremos entender la programación funcional son las funciones puras . Pero, ¿qué significa esto realmente? ¿Qué hace que una función sea pura?
Entonces, ¿cómo sabemos si una función es pure
o no? Aquí hay una definición muy estricta de pureza:
- Devuelve el mismo resultado si se le dan los mismos argumentos (también se le conoce como
deterministic
) - No causa ningún efecto secundario observable.
Devuelve el mismo resultado si se le dan los mismos argumentos
Imagina que queremos implementar una función que calcula el área de un círculo. Una función impura recibiría radius
como parámetro y luego calcularía radius * radius * PI
. En Clojure, el operador es lo primero, por lo que se radius * radius * PI
convierte en (* radius radius PI)
:
¿Por qué es esta una función impura? Simplemente porque usa un objeto global que no se pasó como parámetro a la función.
Ahora imagine que algunos matemáticos argumentan que el PI
valor es en realidad 42
y cambie el valor del objeto global.
Nuestra función impura ahora resultará en 10 * 10 * 42
= 4200
. Para el mismo parámetro ( radius = 10
), tenemos un resultado diferente. ¡Arreglemoslo!
TA-DA?! Ahora siempre pasaremos el I
valor P como parámetro a la función. Entonces ahora solo estamos accediendo a los parámetros pasados a la función. No external object.
- Para los parámetros
radius = 10
&PI = 3.14
, siempre tendremos el mismo resultado:314.0
- Para los parámetros
radius = 10
&PI = 42
, siempre tendremos el mismo resultado:4200
Lectura de archivos
Si nuestra función lee archivos externos, no es una función pura, el contenido del archivo puede cambiar.
Generación de números aleatorios
Cualquier función que se base en un generador de números aleatorios no puede ser pura.
No causa ningún efecto secundario observable.
Los ejemplos de efectos secundarios observables incluyen la modificación de un objeto global o un parámetro pasado por referencia.
Ahora queremos implementar una función para recibir un valor entero y devolver el valor aumentado en 1.
Tenemos el counter
valor. Nuestra función impura recibe ese valor y reasigna el contador con el valor aumentado en 1.
Observación : se desaconseja la mutabilidad en la programación funcional.
Estamos modificando el objeto global. ¿Pero cómo lo haríamos pure
? Simplemente devuelva el valor aumentado en 1. Tan simple como eso.
Vea que nuestra función pura increase-counter
devuelve 2, pero el counter
valor sigue siendo el mismo. La función devuelve el valor incrementado sin alterar el valor de la variable.
Si seguimos estas dos simples reglas, será más fácil comprender nuestros programas. Ahora todas las funciones están aisladas y no pueden afectar a otras partes de nuestro sistema.
Las funciones puras son estables, consistentes y predecibles. Dados los mismos parámetros, las funciones puras siempre devolverán el mismo resultado. No necesitamos pensar en situaciones en las que el mismo parámetro tiene resultados diferentes, porque nunca sucederá.
Beneficios de las funciones puras
Definitivamente, el código es más fácil de probar. No necesitamos burlarnos de nada. Entonces podemos probar funciones puras unitarias con diferentes contextos:
- Dado un parámetro
A
→ esperar que la función devuelva un valorB
- Dado un parámetro
C
→ esperar que la función devuelva un valorD
Un ejemplo simple sería una función para recibir una colección de números y esperar que incremente cada elemento de esta colección.
Recibimos la numbers
colección, la usamos map
con la inc
función para incrementar cada número y devolvemos una nueva lista de números incrementados.
Para el input
[1 2 3 4 5]
, lo esperado output
sería [2 3 4 5 6]
.
Inmutabilidad
No cambia con el tiempo o no se puede cambiar.
Cuando los datos son inmutables, su estado no puede cambiardespués de su creación. Si desea cambiar un objeto inmutable, no puede. En su lugar, crea un nuevo objeto con el nuevo valor.
En Javascript usamos comúnmente el for
bucle. Esta siguiente for
declaración tiene algunas variables mutables.
Para cada iteración, cambiamos el i
y el sumOfValue
estado . Pero, ¿cómo manejamos la mutabilidad en la iteración? ¡Recursión! ¡De vuelta a Clojure!
Entonces aquí tenemos la sum
función que recibe un vector de valores numéricos. Los recur
saltos de nuevo al loop
hasta que obtenemos el vector vacío (nuestra recursión base case
). Para cada "iteración" agregaremos el valor al total
acumulador.
Con la recursividad, mantenemos nuestras variablesinmutable.
Observación : ¡Sí! Podemos utilizar reduce
para implementar esta función. Veremos esto en el Higher Order Functions
tema.
También es muy común construir el estado final de un objeto. Imagina que tenemos una cadena y queremos transformar esta cadena en un url slug
.
En programación orientada a objetos en Ruby, crearíamos una clase, digamos, UrlSlugify
. Y esta clase tendrá un slugify!
método para transformar la entrada de cadena en un url slug
.
¡Hermoso! ¡Está implementado! Aquí tenemos una programación imperativa que dice exactamente lo que queremos hacer en cada slugify
proceso: primero en minúsculas, luego elimine los espacios en blanco inútiles y, finalmente, reemplace los espacios en blanco restantes con guiones.
Pero estamos mutando el estado de entrada en este proceso.
Podemos manejar esta mutación haciendo composición de funciones o encadenamiento de funciones. En otras palabras, el resultado de una función se utilizará como entrada para la siguiente función, sin modificar la cadena de entrada original.
Aquí tenemos:
trim
: elimina los espacios en blanco de ambos extremos de una cadenalower-case
: convierte la cadena a minúsculasreplace
: reemplaza todas las instancias de coincidencia con reemplazo en una cadena dada
Combinamos las tres funciones y podemos hacer "slugify"
nuestra cadena.
Hablando de funciones de combinación , podemos usar la comp
función para componer las tres funciones. Vamos a ver:
Transparencia referencial

Implementemos un square function
:
Esta función (pura) siempre tendrá la misma salida, dada la misma entrada.
Pasar “2” como parámetro de la square function
voluntad siempre devuelve 4. Así que ahora podemos reemplazar el (square 2)
por 4. ¡Eso es todo! Nuestra función es referentially transparent
.
Básicamente, si una función produce constantemente el mismo resultado para la misma entrada, es referencialmente transparente.
funciones puras + datos inmutables = transparencia referencial
Con este concepto, algo interesante que podemos hacer es memorizar la función. Imagina que tenemos esta función:
Los (+ 5 8)
iguales 13
. Esta función siempre dará como resultado 13
. Entonces podemos hacer esto:
Y esta expresión siempre resultará en 16
. Podemos reemplazar toda la expresión con una constante numérica y memorizarla.
Funciona como entidades de primera clase

La idea de las funciones como entidades de primera clase es que las funciones también se tratan como valores y se utilizan como datos.
En Clojure es común usarlo defn
para definir funciones, pero esto es solo azúcar sintáctico para (def foo (fn ...))
. fn
devuelve la función en sí. defn
devuelve un var
que apunta a un objeto de función.
Las funciones como entidades de primera clase pueden:
- referirse a él desde constantes y variables
- pasarlo como parámetro a otras funciones
- devolverlo como resultado de otras funciones
La idea es tratar las funciones como valores y pasar funciones como datos. De esta forma podemos combinar diferentes funciones para crear nuevas funciones con nuevo comportamiento.
Imagina que tenemos una función que suma dos valores y luego duplica el valor. Algo como esto:
Ahora una función que resta valores y devuelve el doble:
Estas funciones tienen una lógica similar, pero la diferencia son las funciones de los operadores. Si podemos tratar las funciones como valores y pasarlos como argumentos, podemos construir una función que reciba la función del operador y la use dentro de nuestra función. ¡Vamos a construirlo!
¡Hecho! Ahora tenemos un f
argumento y lo usamos para procesar a
y b
. Pasamos las funciones +
y -
para componer con la double-operator
función y crear un nuevo comportamiento.
Funciones de orden superior
Cuando hablamos de funciones de orden superior, nos referimos a una función que:
- toma una o más funciones como argumentos, o
- devuelve una función como resultado
La double-operator
función que implementamos anteriormente es una función de orden superior porque toma una función de operador como argumento y la usa.
Usted probablemente ha oído hablar filter
, map
y reduce
. Echemos un vistazo a estos.
Filtrar
Dada una colección, queremos filtrar por un atributo. La función de filtro espera un valor true
o false
para determinar si el elemento debe o no debe incluirse en la colección de resultados. Básicamente, si la expresión de devolución de llamada es true
, la función de filtro incluirá el elemento en la colección de resultados. De lo contrario, no lo hará.
Un ejemplo simple es cuando tenemos una colección de números enteros y solo queremos los números pares.
Enfoque imperativo
Una forma imperativa de hacerlo con Javascript es:
- crear un vector vacío
evenNumbers
- iterar sobre el
numbers
vector - empuja los números pares al
evenNumbers
vector
Podemos usar la filter
función de orden superior para recibir la even?
función y devolver una lista de números pares:
Un problema interesante que resolví en Hacker Rank FP Path fue el problema de la matriz de filtros . La idea del problema es filtrar una matriz dada de enteros y generar solo aquellos valores que sean menores que un valor especificado X
.
Una solución de Javascript imperativa para este problema es algo como:
Decimos exactamente lo que nuestra función necesita hacer: iterar sobre la colección, comparar el elemento actual de la colección con x
y enviar este elemento a resultArray
si pasa la condición.
Enfoque declarativo
Pero queremos una forma más declarativa de resolver este problema y usar también la filter
función de orden superior.
Una solución declarativa de Clojure sería algo como esto:
Esta sintaxis parece un poco extraña en primer lugar, pero es fácil de entender.
#(> x
%) es solo una función anónima que recibe e
s x y la compara con cada elemento de la colección n
. % representa el parámetro de la función anónima, en este caso el elemento actual dentro de t he fil
ter.
También podemos hacer esto con mapas. Imagina que tenemos un mapa de personas con su name
y age
. Y queremos filtrar solo a las personas con un valor de edad específico, en este ejemplo, las personas que tienen más de 21 años.
Resumen de código:
- tenemos una lista de personas (con
name
yage
). - tenemos la función anónima
#(< 21 (:age
%)). ¿Recuerda queh
el% representa el elemento actual de la colección? Bueno, el elemento de la colección es un mapa de personas. Si tenemosdo (:age {:name "TK" :age 2
6}), devuelve el valor de edade,
26 en este caso. - filtramos a todas las personas en función de esta función anónima.
Mapa
La idea de mapa es transformar una colección.
Elmap
método transforma una colección aplicando una función a todos sus elementos y construyendo una nueva colección a partir de los valores devueltos.
Consigamos la misma people
colección de arriba. No queremos filtrar por "edad avanzada" ahora. Solo queremos una lista de cadenas, algo así como TK is 26 years old
. Entonces, la cadena final podría ser :name is :age years old
where :name
y :age
are atributos de cada elemento de la people
colección.
De forma imperativa en Javascript, sería:
De forma declarativa de Clojure, sería:
La idea es transformar una colección determinada en una nueva colección.
Otro problema interesante de Hacker Rank fue el problema de la lista de actualizaciones . Solo queremos actualizar los valores de una colección determinada con sus valores absolutos.
Por ejemplo, la entrada [1 2 3 -4 5]
necesita que la salida sea [1 2 3 4 5]
. El valor absoluto de -4
es 4
.
Una solución simple sería una actualización in situ para cada valor de colección.
Usamos la Math.abs
función para transformar el valor en su valor absoluto y realizamos la actualización in situ.
Esta no es una forma funcional de implementar esta solución.
Primero, aprendimos sobre la inmutabilidad. Sabemos lo importante que es la inmutabilidad para que nuestras funciones sean más consistentes y predecibles. La idea es construir una nueva colección con todos los valores absolutos.
En segundo lugar, ¿por qué no utilizar map
aquí para "transformar" todos los datos?
Mi primera idea fue construir una to-absolute
función para manejar solo un valor.
Si es negativo, queremos transformarlo en un valor positivo (el valor absoluto). De lo contrario, no necesitamos transformarlo.
Ahora que sabemos cómo hacer absolute
para un valor, podemos usar esta función para pasar como argumento a la map
función. ¿Recuerda que a higher order function
puede recibir una función como argumento y usarla? ¡Sí, el mapa puede hacerlo!
Guau. ¡Tan hermosa! ?
Reducir
La idea de reducir es recibir una función y una colección, y devolver un valor creado al combinar los elementos.
Un ejemplo común del que habla la gente es obtener el monto total de un pedido. Imagina que estás en un sitio web de compras. Que ha añadido Product 1
, Product 2
, Product 3
, y Product 4
a su carro de la compra (orden). Ahora queremos calcular el monto total del carrito de compras.
De manera imperativa, iteraríamos la lista de pedidos y sumaríamos la cantidad de cada producto al monto total.
Usando reduce
, podemos construir una función para manejar amount sum
y pasarla como argumento a la reduce
función.
Aquí tenemos shopping-cart
la función sum-amount
que recibe la corriente total-amount
y el current-product
objeto para sum
ellos.
La get-total-amount
función se utiliza para reduce
la shopping-cart
mediante el uso de la sum-amount
ya partir de 0
.
Otra forma de obtener la cantidad total es componer map
y reduce
. ¿Qué quiero decir con eso? Podemos usar map
para transformar el shopping-cart
en una colección de amount
valores, y luego usar la reduce
función con +
función.
El get-amount
recibe el objeto de productos y devuelve sólo el amount
valor. Entonces lo que tenemos aquí es [10 30 20 60]
. Y luego reduce
combina todos los elementos sumando. ¡Hermoso!
Echamos un vistazo a cómo funciona cada función de orden superior. Quiero mostrarles un ejemplo de cómo podemos componer las tres funciones en un ejemplo simple.
Hablando de eso shopping cart
, imagina que tenemos esta lista de productos en nuestro pedido:
Queremos la cantidad total de todos los libros en nuestro carrito de compras. Simple como eso. ¿El algoritmo?
- filtrar por tipo de libro
- transformar el carrito de compras en una colección de cantidades usando el mapa
- combinar todos los elementos sumándolos con reducir
¡Hecho! ?
Recursos
He organizado algunos recursos que leí y estudié. Estoy compartiendo los que encontré realmente interesantes. Para obtener más recursos, visite mi repositorio de Github de programación funcional .
- Recursos específicos de Ruby
- Recursos específicos de JavaScript
- Recursos específicos de Clojure
Intros
- Aprendiendo FP en JS
- Introducción a FP con Python
- Descripción general de FP
- Una introducción rápida a JS funcional
- ¿Qué es FP?
- Jerga de programación funcional
Funciones puras
- ¿Qué es una función pura?
- Programación funcional pura 1
- Programación funcional pura 2
Datos inmutables
- DS inmutable para programación funcional
- Por qué el estado mutable compartido es la raíz de todo mal
- Compartición estructural en Clojure: Parte 1
- Compartición estructural en Clojure: Parte 2
- Compartición estructural en Clojure: Parte 3
- Compartición estructural en Clojure: parte final
Funciones de orden superior
- Eloquent JS: funciones de orden superior
- Función divertida y divertida Filtro
- Fun Fun Fun Mapa divertido
- Fun fun funcion divertida Básica Reducir
- Función divertida y divertida Avanzada Reducir
- Funciones de orden superior de Clojure
- Filtro de función pura
- Mapa puramente funcional
- Reducir puramente funcional
Programación declarativa
- Programación declarativa vs imperativa
¡Eso es!
Hola gente, espero que se hayan divertido leyendo esta publicación, ¡y espero que hayan aprendido mucho aquí! Este fue mi intento de compartir lo que estoy aprendiendo.
Aquí está el repositorio con todos los códigos de este artículo.
Ven a aprender conmigo. Estoy compartiendo recursos y mi código en este repositorio de programación funcional de aprendizaje .
Espero que hayas visto algo útil aquí. ¡Y hasta la próxima! :)
Mi Twitter y Github. ☺
TK.