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 funciones puras. Estos conceptos le permiten 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 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
:
let PI = 3.14; const calculateArea = (radius) => radius * radius * PI; calculateArea(10); // returns 314.0
¿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!
let PI = 3.14; const calculateArea = (radius, pi) => radius * radius * pi; calculateArea(10, PI); // returns 314.0
Ahora siempre pasaremos el valor de PI
como parámetro a la función. Entonces ahora solo estamos accediendo a los parámetros pasados a la función. No se external object
.
- Para los parámetros
radius = 10
yPI = 3.14
, siempre tendremos el mismo resultado:314.0
- Para los parámetros
radius = 10
yPI = 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.
const charactersCounter = (text) => `Character count: ${text.length}`; function analyzeFile(filename) { let fileContent = open(filename); return charactersCounter(fileContent); }
Generación de números aleatorios
Cualquier función que se base en un generador de números aleatorios no puede ser pura.
function yearEndEvaluation() { if (Math.random() > 0.5) { return "You get a raise!"; } else { return "Better luck next year!"; } }
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.
let counter = 1; function increaseCounter(value) { counter = value + 1; } increaseCounter(counter); console.log(counter); // 2
Tenemos el counter
valor. Nuestra función impura recibe ese valor y reasigna el contador con el valor aumentado en 1.
let counter = 1; const increaseCounter = (value) => value + 1; increaseCounter(counter); // 2 console.log(counter); // 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.
Vea que nuestra función pura increaseCounter
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.
let list = [1, 2, 3, 4, 5]; const incrementNumbers = (list) => list.map(number => number + 1);
Recibimos la numbers
matriz, la usamos map
para incrementar cada número y devolvemos una nueva lista de números incrementados.
incrementNumbers(list); // [2, 3, 4, 5, 6]
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, suel estado no puede cambiardespués de su creación.Si desea cambiar un objeto inmutable, no puede. En lugar,crea un nuevo objeto con el nuevo valor.
In JavaScript we commonly use the for
loop. This next for
statement has some mutable variables.
var values = [1, 2, 3, 4, 5]; var sumOfValues = 0; for (var i = 0; i < values.length; i++) { sumOfValues += values[i]; } sumOfValues // 15
For each iteration, we are changing the i
and the sumOfValue
state. But how do we handle mutability in iteration? Recursion.
let list = [1, 2, 3, 4, 5]; let accumulator = 0; function sum(list, accumulator) { if (list.length == 0) { return accumulator; } return sum(list.slice(1), accumulator + list[0]); } sum(list, accumulator); // 15 list; // [1, 2, 3, 4, 5] accumulator; // 0
So here we have the sum
function that receives a vector of numerical values. The function calls itself until we get the list empty (our recursion base case
). For each "iteration" we will add the value to the total
accumulator.
With recursion, we keep our variablesimmutable. The list
and the accumulator
variables are not changed. It keeps the same value.
Observation: We can use reduce
to implement this function. We will cover this in the higher order functions topic.
It is also very common to build up the final state of an object. Imagine we have a string, and we want to transform this string into a url slug
.
In Object Oriented Programming in Ruby, we would create a class, let’s say, UrlSlugify
. And this class will have a slugify
method to transform the string input into a url slug
.
class UrlSlugify attr_reader :text def initialize(text) @text = text end def slugify! text.downcase! text.strip! text.gsub!(' ', '-') end end UrlSlugify.new(' I will be a url slug ').slugify! # "i-will-be-a-url-slug"
It’s implemented!
Here we have imperative programming saying exactly what we want to do in each slugify
process — first lower-case, then remove useless white spaces and, finally, replace remaining white spaces with hyphens.
But we are mutating the input state in this process.
We can handle this mutation by doing function composition, or function chaining. In other words, the result of a function will be used as an input for the next function, without modifying the original input string.
const string = " I will be a url slug "; const slugify = string => string .toLowerCase() .trim() .split(" ") .join("-"); slugify(string); // i-will-be-a-url-slug
Here we have:
toLowerCase
: converts the string to all lower casetrim
: removes white-space from both ends of a stringsplit
andjoin
: replaces all instances of match with replacement in a given string
We combine all these 4 functions and we can "slugify"
our string.
Referential transparency

Let’s implement a square function
:
const square = (n) => n * n;
This pure function will always have the same output, given the same input.
square(2); // 4 square(2); // 4 square(2); // 4 // ...
Passing 2
as a parameter of the square function
will always returns 4. So now we can replace the square(2)
with 4. Our function is referentially transparent
.
Basically, if a function consistently yields the same result for the same input, it is referentially transparent.
pure functions + immutable data = referential transparency
With this concept, a cool thing we can do is to memoize the function. Imagine we have this function:
const sum = (a, b) => a + b;
And we call it with these parameters:
sum(3, sum(5, 8));
The sum(5, 8)
equals 13
. This function will always result in 13
. So we can do this:
sum(3, 13);
And this expression will always result in 16
. We can replace the entire expression with a numerical constant and memoize it.
Functions as first-class entities

The idea of functions as first-class entities is that functions are also treated as values and used as data.
Functions as first-class entities can:
- refer to it from constants and variables
- pass it as a parameter to other functions
- return it as result from other functions
The idea is to treat functions as values and pass functions like data. This way we can combine different functions to create new functions with new behavior.
Imagine we have a function that sums two values and then doubles the value. Something like this:
const doubleSum = (a, b) => (a + b) * 2;
Now a function that subtracts values and the returns the double:
const doubleSubtraction = (a, b) => (a - b) * 2;
These functions have similar logic, but the difference is the operators functions. If we can treat functions as values and pass these as arguments, we can build a function that receives the operator function and use it inside our function.
const sum = (a, b) => a + b; const subtraction = (a, b) => a - b; const doubleOperator = (f, a, b) => f(a, b) * 2; doubleOperator(sum, 3, 1); // 8 doubleOperator(subtraction, 3, 1); // 4
Now we have an f
argument, and use it to process a
and b
. We passed the sum
and subtraction
functions to compose with the doubleOperator
function and create a new behavior.
Higher-order functions
When we talk about higher-order functions, we mean a function that either:
- takes one or more functions as arguments, or
- returns a function as its result
The doubleOperator
function we implemented above is a higher-order function because it takes an operator function as an argument and uses it.
You’ve probably already heard about filter
, map
, and reduce
. Let's take a look at these.
Filter
Given a collection, we want to filter by an attribute. The filter function expects a true
or false
value to determine if the element should or should not be included in the result collection. Basically, if the callback expression is true
, the filter function will include the element in the result collection. Otherwise, it will not.
A simple example is when we have a collection of integers and we want only the even numbers.
Imperative approach
An imperative way to do it with JavaScript is to:
- create an empty array
evenNumbers
- iterate over the
numbers
array - push the even numbers to the
evenNumbers
array
var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; var evenNumbers = []; for (var i = 0; i < numbers.length; i++) { if (numbers[i] % 2 == 0) { evenNumbers.push(numbers[i]); } } console.log(evenNumbers); // (6) [0, 2, 4, 6, 8, 10]
We can also use the filter
higher order function to receive the even
function, and return a list of even numbers:
const even = n => n % 2 == 0; const listOfNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; listOfNumbers.filter(even); // [0, 2, 4, 6, 8, 10]
One interesting problem I solved on Hacker Rank FP Path was the Filter Array problem. The problem idea is to filter a given array of integers and output only those values that are less than a specified value X
.
An imperative JavaScript solution to this problem is something like:
var filterArray = function(x, coll) { var resultArray = []; for (var i = 0; i < coll.length; i++) { if (coll[i] < x) { resultArray.push(coll[i]); } } return resultArray; } console.log(filterArray(3, [10, 9, 8, 2, 7, 5, 1, 3, 0])); // (3) [2, 1, 0]
We say exactly what our function needs to do — iterate over the collection, compare the collection current item with x
, and push this element to the resultArray
if it pass the condition.
Declarative approach
But we want a more declarative way to solve this problem, and using the filter
higher order function as well.
A declarative JavaScript solution would be something like this:
function smaller(number) { return number < this; } function filterArray(x, listOfNumbers) { return listOfNumbers.filter(smaller, x); } let numbers = [10, 9, 8, 2, 7, 5, 1, 3, 0]; filterArray(3, numbers); // [2, 1, 0]
Using this
in the smaller
function seems a bit strange in the first place, but is easy to understand.
this
will be the second parameter in the filter
function. In this case, 3
(the x
) is represented by this
. That's it.
We can also do this with maps. Imagine we have a map of people with their name
and age
.
let people = [ { name: "TK", age: 26 }, { name: "Kaio", age: 10 }, { name: "Kazumi", age: 30 } ];
And we want to filter only people over a specified value of age, in this example people who are more than 21 years old.
const olderThan21 = person => person.age > 21; const overAge = people => people.filter(olderThan21); overAge(people); // [{ name: 'TK', age: 26 }, { name: 'Kazumi', age: 30 }]
Summary of code:
- we have a list of people (with
name
andage
). - we have a function
olderThan21
. In this case, for each person in people array, we want to access theage
and see if it is older than 21. - we filter all people based on this function.
Map
The idea of map is to transform a collection.
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:
var people = [ { name: "TK", age: 26 }, { name: "Kaio", age: 10 }, { name: "Kazumi", age: 30 } ]; var peopleSentences = []; for (var i = 0; i < people.length; i++) { var sentence = people[i].name + " is " + people[i].age + " years old"; peopleSentences.push(sentence); } console.log(peopleSentences); // ['TK is 26 years old', 'Kaio is 10 years old', 'Kazumi is 30 years old']
De una manera declarativa de JavaScript, sería:
const makeSentence = (person) => `${person.name} is ${person.age} years old`; const peopleSentences = (people) => people.map(makeSentence); peopleSentences(people); // ['TK is 26 years old', 'Kaio is 10 years old', 'Kazumi is 30 years old']
La idea es transformar una matriz dada en una nueva matriz.
Otro problema interesante de Hacker Rank fue el problema de la lista de actualizaciones. Solo queremos actualizar los valores de una matriz dada con sus valores absolutos.
For example, the input [1, 2, 3, -4, 5]
needs the output to be [1, 2, 3, 4, 5]
. The absolute value of -4
is 4
.
A simple solution would be an in-place update for each collection value.
var values = [1, 2, 3, -4, 5]; for (var i = 0; i < values.length; i++) { values[i] = Math.abs(values[i]); } console.log(values); // [1, 2, 3, 4, 5]
We use the Math.abs
function to transform the value into its absolute value, and do the in-place update.
This is not a functional way to implement this solution.
First, we learned about immutability. We know how immutability is important to make our functions more consistent and predictable. The idea is to build a new collection with all absolute values.
Second, why not use map
here to "transform" all data?
My first idea was to test the Math.abs
function to handle only one value.
Math.abs(-1); // 1 Math.abs(1); // 1 Math.abs(-2); // 2 Math.abs(2); // 2
We want to transform each value into a positive value (the absolute value).
Now that we know how to do absolute
for one value, we can use this function to pass as an argument to the map
function. Do you remember that a higher order function
can receive a function as an argument and use it? Yes, map can do it!
let values = [1, 2, 3, -4, 5]; const updateListMap = (values) => values.map(Math.abs); updateListMap(values); // [1, 2, 3, 4, 5]
Wow. So beautiful!
Reduce
The idea of reduce is to receive a function and a collection, and return a value created by combining the items.
A common example people talk about is to get the total amount of an order. Imagine you were at a shopping website. You’ve added Product 1
, Product 2
, Product 3
, and Product 4
to your shopping cart (order). Now we want to calculate the total amount of the shopping cart.
In imperative way, we would iterate the order list and sum each product amount to the total amount.
var orders = [ { productTitle: "Product 1", amount: 10 }, { productTitle: "Product 2", amount: 30 }, { productTitle: "Product 3", amount: 20 }, { productTitle: "Product 4", amount: 60 } ]; var totalAmount = 0; for (var i = 0; i < orders.length; i++) { totalAmount += orders[i].amount; } console.log(totalAmount); // 120
Using reduce
, we can build a function to handle the amount sum
and pass it as an argument to the reduce
function.
let shoppingCart = [ { productTitle: "Product 1", amount: 10 }, { productTitle: "Product 2", amount: 30 }, { productTitle: "Product 3", amount: 20 }, { productTitle: "Product 4", amount: 60 } ]; const sumAmount = (currentTotalAmount, order) => currentTotalAmount + order.amount; const getTotalAmount = (shoppingCart) => shoppingCart.reduce(sumAmount, 0); getTotalAmount(shoppingCart); // 120
Here we have shoppingCart
, the function sumAmount
that receives the current currentTotalAmount
, and the order
object to sum
them.
The getTotalAmount
function is used to reduce
the shoppingCart
by using the sumAmount
and starting from 0
.
Another way to get the total amount is to compose map
and reduce
. What do I mean by that? We can use map
to transform the shoppingCart
into a collection of amount
values, and then just use the reduce
function with sumAmount
function.
const getAmount = (order) => order.amount; const sumAmount = (acc, amount) => acc + amount; function getTotalAmount(shoppingCart) { return shoppingCart .map(getAmount) .reduce(sumAmount, 0); } getTotalAmount(shoppingCart); // 120
The getAmount
receives the product object and returns only the amount
value. So what we have here is [10, 30, 20, 60]
. And then the reduce
combines all items by adding up. Beautiful!
We took a look at how each higher order function works. I want to show you an example of how we can compose all three functions in a simple example.
Talking about shopping cart
, imagine we have this list of products in our order:
let shoppingCart = [ { productTitle: "Functional Programming", type: "books", amount: 10 }, { productTitle: "Kindle", type: "eletronics", amount: 30 }, { productTitle: "Shoes", type: "fashion", amount: 20 }, { productTitle: "Clean Code", type: "books", amount: 60 } ]
We want the total amount of all books in our shopping cart. Simple as that. The algorithm?
- filter by book type
- transform the shopping cart into a collection of amount using map
- combine all items by adding them up with reduce
let shoppingCart = [ { productTitle: "Functional Programming", type: "books", amount: 10 }, { productTitle: "Kindle", type: "eletronics", amount: 30 }, { productTitle: "Shoes", type: "fashion", amount: 20 }, { productTitle: "Clean Code", type: "books", amount: 60 } ] const byBooks = (order) => order.type == "books"; const getAmount = (order) => order.amount; const sumAmount = (acc, amount) => acc + amount; function getTotalAmount(shoppingCart) { return shoppingCart .filter(byBooks) .map(getAmount) .reduce(sumAmount, 0); } getTotalAmount(shoppingCart); // 70
Done!
Resources
I’ve organised some resources I read and studied. I’m sharing the ones that I found really interesting. For more resources, visit my Functional Programming Github repository
- EcmaScript 6 course by Wes Bos
- JavaScript by OneMonth
- Ruby specific resources
- Javascript specific resources
- Clojure specific resources
- Learn React by building an App
Intros
- Learning FP in JS
- Intro do FP with Python
- Overview of FP
- A quick intro to functional JS
- What is FP?
- Functional Programming Jargon
Pure functions
- What is a pure function?
- Pure Functional Programming 1
- Pure Functional Programming 2
Immutable data
- Immutable DS for functional programming
- Why shared mutable state is the root of all evil
Higher-order functions
- Eloquent JS: Higher Order Functions
- Fun fun function Filter
- Fun fun function Map
- Fun fun function Basic Reduce
- Fun fun function Advanced Reduce
- Clojure Higher Order Functions
- Purely Function Filter
- Purely Functional Map
- Purely Functional Reduce
Declarative Programming
- Declarative Programming vs Imperative
That’s it!
Hey people, I hope you had fun reading this post, and I hope you learned a lot here! This was my attempt to share what I’m learning.
Here is the repository with all codes from this article.
Come learn with me. I’m sharing resources and my code in this Learning Functional Programming repository.
I also wrote an FP post but using mainly Clojure
I hope you saw something useful to you here. And see you next time! :)
My Twitter & Github.
TK.