Softtek Softtek
  • Our experience
  • Overview
  • Insights
  • Blog
  • Newsroom
  • Careers
  • Contact us
softtek Language Selector
ENGLISH
EUROPE / EN
ESPAÑOL
EUROPA / ES
PORTUGUÊS
中文(简体)
Search button
AI
APPROACH
INDUSTRIES
SERVICES & SOLUTIONS
TRANSCEND
Softtek GenAI
FRIDA AI for Software Engineering
Service Transformation
Portfolio Transformation
Digital Acceleration
Our Work
Agribusiness
Airlines
Automotive
Banking & Financial Services
Consumer Packaged Goods
Energy & Utilities
Fitness & Wellness
Gaming
Government & Public Sector
Higher Education
Healthcare
Industrial
Insurance
Media & Entertainment
Oil & Gas
Pharma & Beauty
Professional Sports
Restaurant & Hospitality
Retail
Technology
Telecommunications
Transportation & Logistics
Digital Solutions
Digital Optimization
Digital Sales
Data Masking Solution
IT Cost Optimization
Fan Engagement Ecosystem
Softtek Digital Enablers
DIEGO
blauLabs
Business OnDemand
Click2Sync Omnichannel
Automotive Digital Assistant
Guest Engagement
Socializer
Collaborative Commuting
Workplace Management
Application Services
Software Development
Quality Engineering
Application Management
Application Services
Cloud & DevOps
Cloud Services
IT Infrastructure
Digital Security
DevOps
Data & Automation
Data and AI
Intelligent Automation
Services Transformation
Core Modernization
Next-Gen IT Operations
Platform Services
AWS
SAP
Microsoft
Salesforce
ServiceNow
Atlassian
BlueYonder
Sustainability by Softtek
Softtek
Language selector
search button
AI
Softtek GenAI
FRIDA AI for Software Engineering
APPROACH
Service Transformation
Portfolio Transformation
Digital Acceleration
Our Work
INDUSTRIES
Agribusiness
Airlines
Automotive
Banking & Financial Services
Consumer Packaged Goods
Energy & Utilities
Fitness & Wellness
Gaming
Government & Public Sector
Higher Education
Healthcare
Industrial
Insurance
Media & Entertainment
Oil & Gas
Pharma & Beauty
Professional Sports
Restaurant & Hospitality
Retail
Technology
Telecommunications
Transportation & Logistics
SERVICES & SOLUTIONS
Digital Solutions
Digital Optimization
Digital Sales
Data Masking Solution
IT Cost Optimization
Fan Engagement Ecosystem
Softtek Digital Enablers
DIEGO
blauLabs
Business OnDemand
Click2Sync Omnichannel
Automotive Digital Assistant
Guest Engagement
Socializer
Collaborative Commuting
Workplace Management
Application Services
Software Development
Quality Engineering
Application Management
Application Services
Cloud & DevOps
Cloud Services
IT Infrastructure
Digital Security
DevOps
Data & Automation
Data and AI
Intelligent Automation
Services Transformation
Core Modernization
Next-Gen IT Operations
Platform Services
AWS
SAP
Microsoft
Salesforce
ServiceNow
Atlassian
BlueYonder
TRANSCEND
Sustainability by Softtek
Our experience
Overview
Insights
Blog
Newsroom
Careers
Contact us
Presencia Global
ENGLISH
EUROPE / EN
ESPAÑOL
EUROPA / ES
PORTUGUÊS
中文(简体)
Softtek Blog

Programación funcional con Javascript. (Parte V)

Autor
Author Cesar Sapetti
Publicado el:
mar 5, 2019
Tiempo de lectura:
mar 2019
|
SHARE
Share on LinkedIn
Share on X
Share on Facebook
SHARE
Share on LinkedIn
Share on X
Share on Facebook

 

Continuamos con nuestra colección de posts sobre Programación funcional con Javascript (parte I, parte II, parte III y parte IV). En esta quinta entrada volveremos a ver algo más de teoría antes de ver en un último post la práctica, en el que veremos cómo solucionar problemas de tareas asíncronas, o control de errores, que nos quedaba pendiente.

Pero antes de nada os invitamos a tener la mente abierta, ya que nos encontraremos con vocabulario nuevo al que, quizás, no nos hemos enfrentado antes. Pero tranquilos, iremos poco a poco avanzando sobre algunos de los conceptos básicos más teóricos, y luego veremos ejemplos prácticos del día a día.

Siéntate y disfruta de la lectura. No será hasta casi el final del post cuando veremos realmente aplicados algunos de los conceptos.

Containers

Éste no es en sí uno de esos conceptos con nombres extraños que veremos, pero si estará presente en todos ellos.

Definiremos un contenedor para almacenar un valor e interactuar con él; tendremos que vivir con que, en algunos casos, introduciremos el valor en el contenedor y no lo volveremos a extraer de ahí. Recordad que hay que tener la mente abierta.

De alguna manera, ésto ya pasa cuando realizamos alguna aplicación con programación orientada a objetos: introducíamos valores a través de setters en una clase y durante la ejecución de ese proceso no salían de ahí. Sí, teníamos getters a través de los cuales obtener esos valores, pero seguramente pasaban a otra clase tras algún cálculo. Dejemos ésto aparte y veamos cómo sería un contenedor.

const Container = function(x) {
  this.value = x
}

Bastante sencillo. Como se ve arriba, el contenedor recibirá un valor y lo almacenará. Ahora vamos a crear un método en nuestro contenedor que nos ayude a almacenar el valor.

Container.of = (x) => new Container(x)

Y, a continuación, vamos a probarlo y ver cómo lo almacena.

Container.of(10) // { "value": 10 }
Container.of('Hello World') // { "value": "Hello World" }
Container.of([ 1, 2, 3 ]) // { "value": [1, 2, 3] }
Container.of({ a: 1, b: 2, c: 3 }) // { "value": { "a": 1, "b": 2, "c": 3 }

En los contenedores podemos almacenar cualquier tipo, incluso y, aunque no está mencionado arriba, podríamos almacenar funciones (first class functions: las funciones son valores). Incluso ir un paso más allá y almacenar otro contenedor.

Container.of(Container.of('two deep level'))
// { "value": { "value": "two deep level" } }

 

Functors

Empezamos a ver algo de vocabulario nuevo en este caso, a no ser que los hayas conocido a través del algebra. Pero, y para simplificar las cosas, os diré que no solo ya hemos visto un Functor en esta serie de FP, sino que además si ya has trabajado antes con JS, llevarás usándolos todo ese tiempo. Los arrays son Functors. Lo que convierte a un Array en un Functor es que implementan map. Además para considerarse Functor, la implementación de map debe seguir unas leyes.

Veamos esas leyes y cómo implementamos map en nuestro contenedor para casos sencillos:

Para hablar de la primera ley necesitaremos la función identity, ¿recordáis esa función que veíamos en Ramda llamada identity? Devolvía el mismo valor que se le pasaba. Nos vendrá bien para esta explicación.

// identity = x => x
identity(10) // 10

Esta ley dice que aplicar la función identity a través de map sobre un contenedor equivale a hacer identity directamente sobre el contenedor. Veámoslo tal cual funciona con array antes de verlo con nuestro propio contenedor.

const mapArray = [ 1, 2, 3, 4, 5 ]
mapArray.map(identity) // [ 1, 2, 3, 4, 5 ]
identity(mapArray) // [ 1, 2, 3, 4, 5 ]

Ambas expresiones devuelven un resultado equivalente. Veamos ahora su implementación y el resultado con nuestro contenedor.

Container.prototype.map = function(fn) {
  return Container.of(fn(this.value))
} 

Map aplica la función que se le pasa sobre el valor actual y devuelve su resultado en un nuevo contenedor; al igual que hace map en array, que nos devuelve un nuevo array en cada uso. Bastante sencillo hasta aquí. Veamos si la ley de identidad se aplica en nuestro contenedor de la misma forma que en array.

const c = Container.of(10)
c.map(identity) // { "value": 10 }
// equivale a
identity(c) // { "value": 10 }

Parece que sí, que cumplimos esta ley.

La segunda ley dice que al ejecutar compose con dos argumentos, cada uno de ellos una función, dentro del map del contenedor equivale a ejecutar map sobre el contenedor con una de las funciones y a su resultado aplicarle de nuevo map con la otra función … mejor veámoslo con algo mucho más legible:


// myFunctor.map(compose(f, g))
// equivale a
// myFunctor.map(g).map(f)

Probémoslo como hicimos antes con Array.

const mapArray = [ 1, 2, 3 ]
mapArray.map(compose(concat('a', add(10)) // [ "a11", "a12", "a13" ]
mapArray.map(add(10)).map(concat('a')) // [ "a11", "a12", "a13" ]

Y ahora con nuestro contenedor.

const c = Container.of(1)
c.map(compose(concat('a'), add(10))) // { "value": "a11" }
c.map(add(10)).map(concat('a')) // { "value": "a11" }

Pues también se cumple. Nuestro contenedor es un Functor.

Además de Array, las promesas de ES6 (que comentábamos en un post anterior) también se consideran Functors, no implementan un método con el nombre map pero el método then se comporta como tal. No será la única vez que mismo concepto es nombrado de formas diferentes.

Monads

Antes de explicar nada sobre los Monads, expliquemos un problema con el que nos encontraremos al trabajar con los contenedores. Ya veíamos antes que un contenedor podría contener otro como valor.

Container.of(Container.of(1)) // { "value": { "value": 2 } }

Esto podría darse en los que tengamos una función que devuelva a su vez un contenedor y queramos usarla sobre nuestro contenedor. Estos casos se darán a medida que el código vaya siendo más complejo. No crearemos un contenedor de esa manera, pero ocurrirá cuando queramos usar map sobre el contendor.

 

Veamos el siguiente ejemplo.

const concatContainers = x => Container.of(concat('Hello ', x))
Container.of('World').map(concatContainers)

Tenemos una función que retornará un contenedor con la concatenación de ‘Hello’ con el valor que le pasemos por argumentos. Así que al contenedor que se crea en map, le establecemos como valor el contenedor que se devuelve en nuestra función concatContainers. Al final el resultado es:

{ "value": { "value": "Hello World" } }

Solo necesitaríamos un contenedor, pero ahora tiene 2 niveles, en la siguiente 3, etc. Es aquí donde necesitaremos los Monads.

Relacionado con los Monads veréis por todos lados la frase de James Iry: “A monad is just a monoid in the category of endoFunctors, what's the problem?”. Entiendo que sería en tono irónico pero, lejos de ayudar, asusta. Por ello y para simplificarlo, vamos a dejarlo en que un Monad es un contenedor que implementa chain, quizás más conocido por flatMap aunque también lo llaman bind (no confundir con el método bind de las funciones que hay en JS) en algunos lenguajes como Haskell.

Vamos a ver cómo sería la implementación de chain.

Container.prototype.chain = function(fn) {
  return fn(this.value)
}

Si lo comparamos con la implementación de map, veremos que la diferencia es que map hace uso del método of para devolver un contenedor nuevo y chain devuelve el valor tras aplicar la función que se pasa por parámetros.

Veamos a continuación las leyes que debe seguir un Monad:

// Monad.of(aValue).chain(Monad.of)
//equivale a
// Monad.of(aValue)

Un Monad al que hacemos chain con una función equivale a esa función aplicada directamente sobre el valor del Monad. Tiene sentido, pero veámoslo con nuestro Container.

Container.of('some value').chain(toUpper) // "SOME VALUE"
// equivale a
toUpper('some value') // "SOME VALUE"

Vemos que se cumple, así que probamos la ley de nuevo con nuestro contenedor.

Container.of('some value').chain(Container.of) // { "value": "some value" }
// equivale a
Container.of('some value') // { "value": "some value" }

Y lo probamos sobre nuestro contenedor.

También se cumple. Nuestro contenedor ahora también es un Monad.

Así que vamos a probar el ejemplo que veíamos antes.

const concatContainers = x => Container.of(concat('Hello ', x))
Container.of('World').chain(concatContainers)
// { "value": "Hello World" }

Ahí está, el contendor solo contiene un valor y no otro contenedor.

Antes de pasar a ejemplos prácticos en el siguiente post, veamos un último tipo.

Aplicatives

En comparación con los anteriores, su nombre no solo es más sencillo sino que además nos da algo más de información.

Ahora digamos que queremos ejecutar una función sobre dos contenedores.

concat(Container.of('Hello '), Container.of('World'))
// { "value": "Hello " } does not have a method named "concat"
add(Container.of(2), Container.of(3))
// NaN

Los contenedores no son del tipo String ni Number. Usando una mezcla de chain con una función que a su vez tenga un map funcionaría.

Container.of('Hello ').chain(hello => Container.of('world').map(concat(hello)))
// { "value": "Hello World" }
Container.of(2).chain(two => Container.of(3).map(add(two)))
// { "value": 5 }

La contra de realizar la operación de esta manera, es que el contenedor interno no se evaluará hasta que lo haga el contendor externo.

Para ello los Aplicatives implementan el método ap. Veamos cómo implementarlo:

Container.prototype.ap = function(other_container) {
  return other_container.map(this.value)
}

Como vemos ap espera como argumento otro contenedor sobre el que hará map con, ojo aquí, la función que le hayamos pasado al contenedor para que establezca como su valor. Repito, el contenedor dónde llamemos al método ap, deberá contener una función y no un valor.

Vamos a probar el ejemplo anterior con nuestro nuevo método sin pararnos más con los Applicatives.

Container.of(concat).ap(Container.of('Hello ')).ap(Container.of('World'))
// { "value": "Hello World" }
Container.of(add).ap(Container.of(2)).ap(Container.of(3))
// { "value": 5 }

 

Category theory

Llegado este punto, comentar que en todo lo que hemos visto anteriormente hoy no hemos hecho referencia a la Teoría de la Categoría de la que vienen estos conceptos, por lo que  esta ha sido una introducción NO matemática a alguna de sus estructuras algebraicas y de la que, según vayáis investigando, iréis viendo referencias. Además nos hemos saltado otras condiciones,  así como otras estructuras que se destacan en el entorno de FP. La especificación para JavaScript, que en parte he intentado seguir, la podéis encontrar en el link de abajo que hace referencia a Fantasy-Land.

* Teoría de las categorías: https://es.wikipedia.org/wiki/Teor%C3%ADa_de_categor%C3%ADas

* Fantasy-Land spec: https://github.com/fantasyland/fantasy-land

 

Resumen

Estos son los puntos sobre los que, para simplificar en lo visto en esta entrada, deberíamos quedarnos:

  • Los Functors aplican, a través de map, una función a un valor que está en un contenedor.
  • Los Monads aplican,  a través de chain, una función que devuelve un valor que está otro contenedor al valor de otro contenedor.
  • Los Applicatives aplican, a través de ap, funciones que se encuentran dentro de contenedores sobre valores que a su vez están contenidos en otros contenedores.

En la siguiente, y última entrada, veremos aplicaciones a estas estructuras y cómo simplifican ciertos problemas que vemos en todas las aplicaciones.

 

 

Related posts

sep 26, 2017
Programación funcional con Javascript (I)
image
feb 13, 2018
Programación funcional con Javascript (IV)
keyboard-focus
mar 19, 2019
Programación funcional con Javascript. (Parte VI)

Let’s stay in touch!

Get Insights from our experts delivered right to your inbox!

Follow us:
Softtek LinkedIn
Softtek Twitter
Softtek Facebook
Softtek Instagram
Softtek Instagram
Follow us:
Softtek LinkedIn
Softtek Twitter
Softtek Facebook
Softtek Instagram
Softtek Instagram

© Valores Corporativos Softtek S.A. de C.V. 2025.
privacy notice
legal disclaimer
code of ethics
our policies
webmaster@softtek.com