DEV Community

Heiker
Heiker

Posted on Edited on Originally published at vonheikemen.github.io

Un poco del paradigma funcional en tu javascript: TÃĐcnicas de composiciÃģn

Hoy el tema serÃĄ la composiciÃģn de funciones. El arte de crear cosas complejas con piezas "simples". Si no saben nada del paradigma funcional en la programaciÃģn incluso mejor, esta serÃĄ una introducciÃģn a conceptos y patrones de ese paradigma que pueden implementarse en javascript. Lo que voy a presentar no serÃĄ una fÃģrmula mÃĄgica que harÃĄ su cÃģdigo mÃĄs legible, simple y sin errores; así no funcionan las cosas. Sí creo que ayuda en la soluciÃģn de problemas pero para sacarle el mayor provecho deben tener en cuenta ciertas cosas. Así que antes de mostrar cualquier implementaciÃģn vamos a hablar de algunos conceptos y filosofía.

Lo que deben saber

ÂŋQuÃĐ es la composiciÃģn de funciones?

Es un mecanismo que nos permite combinar dos o mÃĄs funciones en una nueva funciÃģn.

Parece una idea simple, ciertamente todos en algÚn momento hemos combinado un par de funciones ÂŋDe verdad pensamos en la composiciÃģn cuando creamos una funciÃģn? ÂŋQuÃĐ nos ayudaría a crear funciones diseÃąadas para ser combinadas?

Filosofía

Repito, la composiciÃģn de funciones es mÃĄs efectiva si siguen ciertos principios.

  • La funciÃģn tiene un sÃģlo propÃģsito, una sola responsabilidad.
  • Asume que el resultado de la funciÃģn serÃĄ consumido por otra.

Probablemente han escuchado eso en algÚn otro lado, es parte de la filosofía unix. ÂŋAlguna vez se han preguntado cÃģmo un lenguage como bash, que tiene una sintaxis un tanto extraÃąa y muchas limitaciones, puede ser tan popular? Esos dos principios son parte de la razÃģn. Una gran parte de los programas que se ejecutan en este ambiente estÃĄn diseÃąados para ser componentes reusables y cuando "conectas" dos o mÃĄs, el resultado es un programa que tambiÃĐn puede ser conectado con otros programas aÚn no conocidos.

Para algunos puede parecer tonto o incluso exagerado tener muchas funciones que solo hacen una cosa, especialmente si esas funciones hacen algo que parece inÚtil, pero puedo demostrarles que cada funciÃģn puede ser valiosa en el contexto adecuado.

Intentemos ilustrar una situaciÃģn donde estos principios se ponen en prÃĄctica.

Nota: De antemano me disculpo por el uso indebido de los comandos cat y grep, esto lo hago para demostrar el valor de la composiciÃģn.

Digamos que queremos extraer el valor de la variable HOST que estÃĄ en un archivo .env, vamos a hacerlo usando bash.

Este sería el archivo.

ENV=development
HOST=http://locahost:5000
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Para mostrar el contenido de ese archivo usamos cat.

cat .env
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Para filtrar el contenido del archivo y buscar la línea que queremos usamos grep, le proveemos el patrÃģn que buscamos y el contenido del archivo.

cat .env | grep "HOST=.*"
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Para obtener el valor que queremos usamos cut. El comando cut va a tomar el resultado de grep y lo va a dividir usando un delimitador, luego le decimos quÃĐ secciÃģn de la cadena queremos.

cat .env | grep "HOST=.*" | cut --delimiter="=" --fields=2
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Eso debería mostrarnos.

http://locahost:5000
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Si colocamos esa cadena de comandos en un script o una funciÃģn en nuestro .bashrc efectivamente tendremos un comando que puede ser usado de la misma manera por otros programas que aÚn no conocemos. Este es el tipo de flexibilidad y poder que queremos lograr.

Espero que en este punto sepan quÃĐ tipo de mentalidad debemos tener al momento de crear una funciÃģn pero aÚn hay una cosa que deben recordar.

Las funciones son cosas

Pongamos nuestra atenciÃģn en javascript. ÂŋHan escuchado la frase "funciones de primera clase"? Significa que las funciones pueden ser tratadas como cualquier otro valor. Vamos a compararlos con los arreglos.

  • Pueden asignarlos a una variable.
const numbers = ['99', '104'];
const repeat_twice = function(str) {
  return str.repeat(2);
};
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
  • Pasarlos como argumento a una funciÃģn.
function map(fn, array) {
  return array.map(fn);
}

map(repeat_twice, numbers);
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
  • Pueden ser retornados por una funciÃģn
function unary(fn) {
  return function(arg) {
    return fn(arg);
  }
}

const safer_parseint = unary(parseInt);

map(safer_parseint, numbers);
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

ÂŋPor quÃĐ les muestro esto? Deben estar conscientes de esta característica de javascript porque vamos a usarla crear funciones auxiliares, como unary, que manipulan otras funciones. Puede que tome un tiempo acostumbrarse a la idea de tratar las funciones como un dato pero definitivamente vale la pena practicarlo ya que es clave para entender muchos de los patrones que se pueden ver en el paradigma funcional.

ComposiciÃģn en la prÃĄctica

Vamos a retomar el ejemplo del archivo .env. Recrearemos lo que hicimos en bash. Primero vamos a intentar un enfoque muy directo, luego exploraremos los defectos de nuestra implementaciÃģn e intentaremos solucionarlos.

Ya hemos hecho esto antes, sabemos lo que debemos hacer. Empecemos por crear una funciÃģn por cada paso.

  • Extraer el contenido del archivo.
const fs = require('fs');

function get_env() {
  return fs.readFileSync('.env', 'utf-8');
}
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
  • Filtrar el contenido basados en un patrÃģn.
function search_host(content) {
  const exp = new RegExp('^HOST=');
  const lines = content.split('\n');

  return lines.find(line => exp.test(line));
}
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
  • Extraer el valor.
function get_value(str) {
  return str.split('=')[1];
}
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Ya estamos listos. Veamos quÃĐ podemos hacer para que estas funciones trabajen juntas.

ComposiciÃģn natural

MencionÃĐ que el primer intento sería un enfoque directo, las funciones ya estÃĄn listas y lo que queda por hacer es ejecutarlas en secuencia.

get_value(search_host(get_env()));
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Digamos que este es el escenario perfecto de una composiciÃģn de funciones, aquí el resultado de una funciÃģn se convierte en la entrada de la siguiente, es el mismo efecto que tiene el símbolo | en bash. A diferencia de bash aquí el flujo de datos va de derecha a izquierda.

Ahora imaginemos que tenemos dos funciones mÃĄs que hacen algo con el valor de HOST.

test(ping(get_value(search_host(get_env()))));
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Las cosas se ponen algo incÃģmodas, todavía esta en un nivel manejable pero la cantidad de parÃĐntesis involucrados ya empieza a molestar. Este sería el momento perfecto para crear una funciÃģn que agrupe esta cadena en una manera mÃĄs legible, pero no haremos eso aÚn, primero buscaremos ayuda.

ComposiciÃģn automÃĄtica

Es aquí donde nuestros conocimientos de las funciones empieza a dar frutos. Lo que haremos para resolver el problema de los parÃĐntesis serÃĄ "automatizar" las llamadas de las funciones. Crearemos una funciÃģn que acepte una lista de funciones, las ejecute una por una y se asegure de pasar el resultado de la funciÃģn anterior como parÃĐmetro a la siguiente.

function compose(...fns) {
  return function _composed(...args) {
    // PosiciÃģn de la Última funciÃģn
    let last = fns.length - 1;

    // Se ejecuta la Última funciÃģn
    // con los parÃĄmetros de `_composed`
    let current_value = fns[last--](...args);

    // recorremos las funciones restantes en orden inverso
    for (let i = last; i >= 0; i--) {
      current_value = fns[i](current_value);
    }

    return current_value;
  };
}
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Ahora podremos hacer esto.

const get_host = compose(get_value, search_host, get_env);

// get_host en realidad es `_composed`
get_host();
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Ya no tenemos el problema de los parÃĐntesis, podemos agregar mÃĄs funciones de manera mÃĄs fÃĄcil y sin entorpecer la legilibilidad.

const get_host = compose(
  test,
  ping,
  get_value,
  search_host,
  get_env
);

get_host();
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Como en nuestro primer intento el flujo de ejecuciÃģn va de derecha a izquierda. Si prefieren invertir el orden sería así.

function pipe(...fns) {
  return function _piped(...args) {
    // Se ejecuta la primera funciÃģn
    // con los parÃĄmetros de `_piped`
    let current_value = fns[0](...args);

    // recorremos las funciones restantes en el orden original
    for (let i = 1; i < fns.length; i++) {
      current_value = fns[i](current_value);
    }

    return current_value;
  };
}
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Ahora pueden leerlo así.

const get_host = pipe(get_env, search_host, get_value);

get_host();
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Todo esto es genial, pero como dije antes lo que tenemos aquí es un escenario ideal. Nuestra composiciÃģn sÃģlo puede manejar funciones que tienen un parÃĄmetro de entrada y una sola línea de ejecuciÃģn (no necesita controlar el flujo de ejecuciÃģn). Eso no es malo, todos deberíamos diseÃąar nuestro cÃģdigo para facilitar ese tipo de situaciones pero como todos sabemos...

No siempre es tan fÃĄcil

Incluso en nuestro ejemplo la Única razÃģn por la que logramos combinar las funciones fue porque incluimos en el cÃģdigo todos los parÃĄmetros necesarios e ignoramos el manejo de errores. Pero no todo estÃĄ perdido, hay formas de sobrepasar las limitaciones que tenemos.

Antes de continuar modificaremos el ejemplo, haremos que sea mÃĄs parecido a la implementaciÃģn en bash.

const fs = require('fs');

function cat(filepath) {
  return fs.readFileSync(filepath, 'utf-8');
}

function grep(pattern, content) {
  const exp = new RegExp(pattern);
  const lines = content.split('\n');

  return lines.find(line => exp.test(line));
}

function cut({ delimiter, fields }, str) {
  return str.split(delimiter)[fields - 1];
}
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

No es exactamente lo mismo que sus contrapartes en bash pero servirÃĄ. Ahora, si quisieramos combinar estas nuevas funciones tendríamos que hacerlo de esta manera.

cut({delimiter: '=', fields: 2}, grep('^HOST=', cat('.env')));
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Funciona pero yo diría que estÃĄ al borde de lo aceptable, aÚn puedo entender lo que estÃĄ pasando pero no querría agregar otra cosa a esa cadena. Si queremos usar pipe tendremos que superar nuestro primer obstÃĄculo.

Funciones con mÚltiples entradas

La soluciÃģn a esto es aplicaciÃģn parcial y por suerte para nosotros javascript tiene un buen soporte incluido para lo que queremos hacer. Nuestro objetivo es simple, pasarle a una funciÃģn una parte de sus parÃĄmetros sin ejecutarla. Queremos ser capaces de hacer algo así.

const get_host = pipe(
  cat,
  grep('^HOST='), 
  cut({ delimiter: '=', fields: 2 })
);

get_host('.env');
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Para replicar este resultado tendremos que recurrir a una tÃĐcnica llamada currying, esta consiste en convertir una funciÃģn de mÚltiples parÃĄmetros en varias funciones de un parÃĄmetro. Bien, para lograrlo lo que debemos hacer es aceptar un parÃĄmetro a la vez devolviendo una funciÃģn por cada parÃĄmetro que necesitamos. Haremos esto con grep y cut.

- function grep(pattern, content) {
+ function grep(pattern) {
+   return function(content) {
      const exp = new RegExp(pattern);
      const lines = content.split('\n');

      return lines.find(line => exp.test(line));
+   }
  }
-
- function cut({ delimiter, fields }, str) {
+ function cut({ delimiter, fields }) {
+   return function(str) {
      return str.split(delimiter)[fields - 1];
+   }
  }
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

En situaciones donde no es posible convertir una funciÃģn normal a una que soporte currying lo que podemos hacer es usar el mÃĐtodo bind que se encuentra en el prototipo de la funciones.

const get_host = pipe(
  cat,
  grep.bind(null, '^HOST='), 
  cut.bind(null, { delimiter: '=', fields: 2 })
);
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Por Último, si todo lo demÃĄs parece muy complicado siempre tenemos la opciÃģn de crear una funciÃģn anÃģnima justo en el lugar.

const get_host = pipe(
  cat,
  content => grep('^HOST=', content), 
  str => cut({ delimiter: '=', fields: 2 }, str)
);
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Eso serÃĄ suficiente para resolver cualquier tipo de problema con funciones que aceptan mÚltiples parÃĄmetros. Sigamos.

Funciones con mÚltiples salidas

ÂŋMÚltiples salidas? Con eso me refiero a funciones que retornan dos (tal vez mÃĄs) tipos de resultados. Esto pasa en funciones que tienen distintos tipos de respuesta dependiendo de cÃģmo las usemos o el contexto donde las usamos. Tenemos ese tipo de funciones en nuestro ejemplo, veamos cat.

function cat(filepath) {
  return fs.readFileSync(filepath, 'utf-8');
}
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Dentro de cat estÃĄ la funciÃģn readFileSync, es la que se encarga de leer el archivo en nuestro sistema, la cual es una operaciÃģn que puede fallar por muchas razones. Entonces cat puede devolver un String si todo sale bien pero tambiÃĐn puede arrojar un error si algo malo ocurre. Tenemos que manejar estos dos casos.

Desafortunadamente para nosotros las excepciones no son lo Único con lo que tenemos que lidiar, tambiÃĐn tenemos que manejar la ausencia de valores. En la funciÃģn grep tenemos esta línea.

lines.find(line => exp.test(line));
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

El mÃĐtodo find se encarga de evaluar cada línea del contenido del archivo. Como pueden imaginar esta operaciÃģn tambiÃĐn puede fallar, simplemente puede darse el caso de que no encuentre el valor que buscamos. A diferencia de readFileSync el mÃĐtodo find no arroja un error, lo que hace es retornar undefined. De por sí undefined no es malo, es sÃģlo que no tenemos ninguna utilidad para ÃĐl. Asumir que el resultado siempre serÃĄ de tipo String es lo que en definitiva causarÃĄ un error.

ÂŋCÚal es la soluciÃģn?

Functors && Monads (me disculpan las palabrotas). Dar una explicaciÃģn apropiada de esos conceptos toma tiempo así que sÃģlo vamos a enfocarnos en lo que nos interesa. Por los momentos pueden pensar en ellos como estructuras que siguen ciertas reglas (pueden encontrar algunas de ellas aquí: Fantasy land).

ÂŋCÃģmo empezamos? Empecemos con los functors.

  • Functors

Vamos a crear una estructura que sea capaz de ejecutar una funciÃģn en el momento adecuado. Ya se han encontrado con una que puede hacer eso: los arreglos. Intenten esto.

const add_one = num => num + 1;
const number = [41];
const empty = [];

number.map(add_one); // => [42]
empty.map(add_one);  // => []
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

ÂŋSe dieron cuenta? map ejecutÃģ add_one sÃģlo una vez, con el arreglo number. No hizo nada en el arreglo vacío, no detuvo la ejecuciÃģn del programa arrojando un error, sÃģlo devolviÃģ un arreglo. Ese es el tipo de comportamiento que queremos.

Repliquemos esto por nuestra cuenta. Vamos a crear una estructura llamada Result, esta representarÃĄ una operaciÃģn que puede o no tener ÃĐxito. TendrÃĄ un mÃĐtodo map que sÃģlo ejecutarÃĄ la funciÃģn que recibe como parÃĄmetro si la operaciÃģn resulta exitosa.

const Result = {};

Result.Ok = function(value) {
  return {
    map: fn => Result.Ok(fn(value)),
  };
}

Result.Err = function(value) {
  return {
    map: () => Result.Err(value),
  };
}
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Tenemos nuestro functor pero ahora se podrían estar preguntando ÂŋEs todo, cÃģmo nos ayuda eso? Lo estamos haciendo un paso a la vez. Usemos lo que tenemos en cat.

function cat(filepath) {
  try {
    return Result.Ok(fs.readFileSync(filepath, 'utf-8'));
  } catch(e) {
    return Result.Err(e);
  }
}
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

ÂŋQuÃĐ ganamos? Intenten esto.

cat('.env').map(console.log);
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Todavía tienen la misma pregunta en su mente, puedo verlo. Ahora intenten incorporar el resto de las funciones.

Nota: Voy a asumir que pueden usar currying para lograr la aplicaciÃģn parcial de los parÃĄmetros.

cat('.env')
  .map(grep('^HOST='))
  .map(cut({ delimiter: '=', fields: 2 }))
  .map(console.log);
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

ÂŋVieron? Esa cadena de maps se parece mucho a compose y pipe. Logramos recuperar la composiciÃģn y le incorporamos manejo de errores (casi).

Quiero hacer algo. Ese patrÃģn que hicimos en el try/catch parece Útil, podríamos extraerlo a una funciÃģn.

 Result.make_safe = function(fn) {
  return function(...args) {
    try {
      return Result.Ok(fn(...args));
    } catch(e) {
      return Result.Err(e);
    }
  }
 }
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Ahora podemos transformar cat sin siquiera tocar su cÃģdigo.

const safer_cat = Result.make_safe(cat);

safer_cat('.env')
  .map(grep('^HOST='))
  .map(cut({ delimiter: '=', fields: 2 }))
  .map(console.log);
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Tal vez quieran hacer algo en caso de error, Âŋcierto? Hagamos que sea posible.

  const Result = {};

  Result.Ok = function(value) {
    return {
      map: fn => Result.Ok(fn(value)),
+     catchMap: () => Result.Ok(value),
    };
  }

  Result.Err = function(value) {
    return {
      map: () => Result.Err(value),
+     catchMap: fn => Result.Err(fn(value)),
    };
  }
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Ahora podemos equivocarnos con confianza.

const safer_cat = Result.make_safe(cat);
const show_error = e => console.error(`Whoops:\n${e.message}`);

safer_cat('what?')
  .map(grep('^HOST='))
  .map(cut({ delimiter: '=', fields: 2 }))
  .map(console.log)
  .catchMap(show_error);
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Sí, lo sÃĐ, todo es muy bonito y Útil pero en algÚn momento van a querer sacar el valor del Result. Entiendo, javascript no es un lenguaje hecho para este tipo de cosas, van a querer "volver a la normalidad". Agregaremos una funciÃģn que nos de la libertad de extraer el valor en cualquier caso.

  const Result = {};

  Result.Ok = function(value) {
    return {
      map: fn => Result.Ok(fn(value)),
      catchMap: () => Result.Ok(value),
+     cata: (error, success) => success(value)
    };
  }

  Result.Err = function(value) {
    return {
      map: () => Result.Err(value),
      catchMap: fn => Result.Err(fn(value)),
+     cata: (error, success) => error(value)
    };
  }
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Con esto podremos elegir quÃĐ hacer al final de la operaciÃģn.

const constant = arg => () => arg;
const identity = arg => arg;

const host = safer_cat('what?')
  .map(grep('^HOST='))
  .map(cut({ delimiter: '=', fields: 2 }))
  .cata(constant("This ain't right"), identity)

// ....
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Nota: Si se preguntan por quÃĐ cata, viene de la palabra catamorfismo, otro de esos tÃĐrminos de teoría de categoría que algunos usan en el paradigma funcional.

Ahora vamos a crear una estructura que nos permita resolver el problema que tenemos con grep. En este caso lo que tenemos que hacer es manejar la ausencia de un valor.

const Maybe = function(value) {
  if(value == null) {
    return Maybe.Nothing();
  }

  return Maybe.Just(value);
}

Maybe.Just = function(value) {
  return {
    map: fn => Maybe.Just(fn(value)),
    catchMap: () => Maybe.Just(value),
    cata: (nothing, just) => just(value)
  };
}

Maybe.Nothing = function() {
  return {
    map: () => Maybe.Nothing(),
    catchMap: fn => fn(),
    cata: (nothing, just) => nothing()
  };
}

Maybe.wrap_fun = function(fn) {
  return function(...args) {
    return Maybe(fn(...args));
  }
}
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Vamos a envolver grep con un Maybe y probaremos si funciona usando el cat original para extraer el contenido del archivo.

const maybe_host = Maybe.wrap_fun(grep('^HOST='));

maybe_host(cat('.env'))
  .map(console.log)
  .catchMap(() => console.log('Nothing()'));
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Eso debería mostrar http://locahost:5000. Y si cambian el patrÃģn ^HOST= debería mostrar Nothing().

Tenemos versiones mÃĄs seguras de cat y grep pero vean lo que pasa cuando se juntan.

safer_cat('.env')
  .map(maybe_host)
  .map(res => console.log({ res }));
  .catchMap(() => console.log('what?'))
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Obtienen esto.

{
  res: {
    map: [Function: map],
    catchMap: [Function: catchMap],
    cata: [Function: cata]
  }
}
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

ÂŋQuÃĐ estÃĄ pasando? Bueno, hay un Maybe atrapado dentro de un Result. Tal vez ustedes no esperaban eso pero otras personas sí, y ellas ya tienen las soluciÃģn.

  • Monads

Resulta que los monads son functors con poderes extra. Lo que nos interesa saber por el momento es que resuelven el problema de las estructuras anidadas. Hagamos los ajustes pertinentes.

  Result.Ok = function(value) {
    return {
      map: fn => Result.Ok(fn(value)),
      catchMap: () => Result.Ok(value),
+     flatMap: fn => fn(value),
      cata: (error, success) => success(value)
    };
  }

  Result.Err = function(value) {
    return {
      map: () => Result.Err(value),
      catchMap: fn => Result.Err(fn(value)),
+     flatMap: () => Result.Err(value),
      cata: (error, success) => error(value)
    };
  }
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
  Maybe.Just = function(value) {
    return {
      map: fn => Maybe.Just(fn(value)),
      catchMap: () => Maybe.Just(value),
+     flatMap: fn => fn(value),
      cata: (nothing, just) => just(value),
    };
  }

  Maybe.Nothing = function() {
    return {
      map: () => Maybe.Nothing(),
      catchMap: fn => fn(),
+     flatMap: () => Maybe.Nothing(),
      cata: (nothing, just) => nothing(),
    };
  }
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

El mÃĐtodo flatMap ademÃĄs de comportarse como map nos permite deshacernos de "capas" extras que pueden complicar la composiciÃģn mÃĄs adelante. AsegÚrense de usar flatMap sÃģlo con funciones que retornen otros monads ya que esta no es la implementaciÃģn mÃĄs segura.

Nota: Sí, los arreglos tambiÃĐn son monads. Tienen los mÃĐtodos map y flatMap que siguen todas las leyes.

Probamos otra vez con maybe_host.

 safer_cat('.env')
  .flatMap(maybe_host)
  .map(res => console.log({ res }));
  .catchMap(() => console.log('what?'))
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Eso debería darnos.

{ res: 'HOST=http://localhost:5000' }
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Estamos listos para combinar todo nuevamente.

const safer_cat = Result.make_safe(cat);
const maybe_host = Maybe.wrap_fun(grep('^HOST='));
const get_value = Maybe.wrap_fun(cut({delimiter: '=', fields: 2}));

const host = safer_cat('.env')
  .flatMap(maybe_host)
  .flatMap(get_value)
  .cata(
    () => 'http://127.0.0.1:3000',
    host => host
  );

// ....
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

ÂŋY cÃģmo sería si quisiÃĐramos usar pipe o compose?

const chain = fn => m => m.flatMap(fn);
const unwrap_or = fallback => fm => 
  fm.cata(() => fallback, value => value);


const safer_cat = Result.make_safe(cat);
const maybe_host = Maybe.wrap_fun(grep('^HOST='));
const get_value = Maybe.wrap_fun(cut({delimiter: '=', fields: 2}));

const get_host = pipe(
  safer_cat,
  chain(maybe_host),
  chain(get_value),
  unwrap_or('http://127.0.0.1:3000')
);

get_host('.env');
💎 āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ’Ž āļŠāļēāļ§āđ€āļ™āđ‡āļ•āđāļŦāđˆāđāļŠāļĢāđŒ: vonheikemen/un poco del paradigma funcional en tu javascript tecnicas de composicion fj - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Pueden ver todo el cÃģdigo aquí: link.

ÂŋTodavía quieren saber mÃĄs?

Hay muchas cosas que no mencionÃĐ para no tomar mucho de su tiempo. Si quieren indagar un poco mÃĄs aquí les dejo mÃĄs material que he preparado.

ConclusiÃģn

Muchas personas hablan de lo lindo que es la composiciÃģn y cÃģmo hace tu cÃģdigo mÃĄs declarativo y limpio, pero nunca te muestran el lado difícil. Espero haber logrado eso, enseÃąarles un poco del lado difícil y cÃģmo se puede superar. Combinar funciones en realidad es un arte, se requiere de prÃĄctica y tiempo para acostumbrarse a ciertas cosas (como que las funciones son cosas).

Fuentes


Gracias por su tiempo. Si este artículo les pareciÃģ Útil y quieren apoyar mis esfuerzos para crear mÃĄs contenido pueden dejar una propina en ko-fi.com/vonheikemen.

buy me a coffee

Top comments (0)