PointFree en javascript

logo

Javascript es realmente muy flexible. Tiene caracteristicas funcionales como de orientacion a objetos y permite la programacion en una gran cantidad de estilos diferentes.
En este post vamos a ver lo que se llama el estilo de programación PointFree y vamos a ir a trabes de algunos escenarios comunes para demostrar sus beneficios.

Un paso atras.

En primer lugar permitanme mostrarles la forma imperativa estandar para extraer informacion de una matriz. El bucle for.

1
2
3
4
5
6
7
8
9
10
// Estilo imperativo
var getAdminEmails = function(users) {
var emails = [];
for (var i = 0; i < users.length; i++) {
if (users[i].role === 'admin') {
emails.push(users[i].email);
}
}
return emails;
}

¿Qué hace esta función? Vamos a analizarla. Tengo un array de emails vacío. Luego recorro todos los usuarios y verifico si rol de un usuario es igual a “admin”, y si es asi añadimos el correo electronico del usuario al array y retornamos el array. ¿bastante familiar no?.

Aunque acabo de describir el codigo de la funcion, todavia no esta muy claro lo que hace. Una manera mucho mas clara de decir lo que haria seria asi:
devuelve todos los mensajes de correo electronico de los usuarios con rol de administrador

Vamos a reescribir esto de una manera un poco mas funcional, utilizando las funciones propias de Javascript como filter y map. (Estoy usando la syntaxis ECS6, tienes que amar estas funciones de flecha) xd.

1
2
3
4
var getAdminEmails = users =>
users
.filter(u => u.role === 'admin')
.map(u => u.email);

El analisis de esta funcion es mucho mas facil.

  1. Se obtienen solamente los usuarios con el rol del admin.
  2. luego se reciben solamente los mensajes de correo electronico.

El primer punto fuerte de este estilo de programacion es que el codigo de la funcion se hacerca mucho mas a la descripcion de lo que hace la funcion . Esto hace que sea mucho mas facil entenderla y razonar sobre ella, y al ver una nueva pieza de codigo se comprende rapidamente lo que se supone que debe hacer dicha funcion sin la necesidad de entrar en detalle sobre como lo hace.

Los filtros y los mapas nos sirven para iterar sobre los elementos de un array, al igual que el bucle for, pero tienen propositos especificos: regresan subconjuntos y transformaciones de elementos , respectivamente.
Por el contrario, cualquier cosa puede suceder dentro de un bucle for. Eso significa que cada vez que te encuentres con un codigo for se tendra que investigar un poco mas para saber si lo que se desea obtener es un subconjunto o no, si se transformara un elemento , si se agregaran los valores , o cualquier combinacion de estos resultados.

Cuando se utilizan filtros y mapas, uno tiene una comprension mas rapida sobre la funcion, y se puede profundizar en la comprension poco a poco a medida que lo necesite. Lo se getAdminEmails retorna un array, ya que tiene filter y map. En una segunda mirada uno ve que primero filtra a los usuarios y luego los transforma.
Ahora uno puede investigar solamente filter y entender que elementos van a ser transformados y comprender que sucede durante la transformacion en si.

Componibilidad

Lo que me parece muy interesante sobre este codigo en la version funcional, es que pude entender facilmente pieza por pieza lo que hace en las diferentes capas.
Voy a tratar de hacerlo aun mas claro ahora.

Vamos a comenzar por tratar de escribir el codigo lo mas cerca posible a lo que hace la funcion. Queremos transformar una lista filtrada, asique vamos a empezar por la composicion de estas 2 piezas.

1
2
3
var getAdminEmails = compose(
getTheEmailsOf,
onlyTheAdminRoleUsers);

compose es una funcion que toma 2 funciones como argumento, y ejecuta una despues de la otra.

su definicion mas simple es asi:

1
2
3
4
5
6
7
8
var compose = (f, g) => x => f(g(x));
//o en ECS5
var compose = function(f,g) {
return function(x) {
return f(g(x));
}
}

Ahora vamos a ver las 2 funciones que van a estar compuestas.

getTheEmailsOf es una funcion que transforma una lista retornando la propiedad email. “Transformar una lista” es lo que hace map y obtener una propiedad de un objeto suena como una tarea muy comun, asique vamos a implementar map desde un punto de vista un poco diferente y prop:

1
2
3
4
5
6
7
var prop = p => x => x[p];
var map = f => list =>
list.map(f);
// Esto da lugar a:
var getTheEmailsOf = map(prop('email'));

onlyTheAdminRoleUsers es un filtro. La prueba para decidir que elementos seran retornados implica probar si una propiedad es igual a algun valor.

1
2
3
4
5
6
7
8
var propEq = v => p => obj =>
prop(p)(obj) === v;
var filter = f => list =>
list.filter(f);
var onlyTheAdminRoleUsers =
filter(propEq('admin')('role'));

Y la version definitiva de la funcion:

1
2
var getAdminEmails = compose(
map( prop('email')),filter(propEq('admin')('role')));

Esto es lo que se llama programacion PointFree o programacion tacita.
Lo que mas sorprende de esta version es que esta enteramente compuesta de otras mas pequeñas genericas y reutilizable funciones. Estas funciones no solamente le ayudaran con sus proximas funciones, sino que le ayudara a comprender mas rapido cualquier funcion.
Despues de saber lo que hace prop, se hace mas facil de entender prop(“algo”) que obj => obj.algo ademas de que no es necesario entrar en el estress de tener que elegir un nombre para la variable temporal obj.

Nota: La mayoria de estas funciones mas pequeñas son genericas y lo suficientemente utiles para utilizarlas en casi todos los proyectos. Ramda es una libreria que tiene exactamente eso: un monton de funciones muy pequeñas y genericas.

Otro ejemplo.

Para ir terminando vamos a ver otra situacion bastante comun.

1
2
3
var calculateTotalPromotions = cart =>
getPromotions(cart.products[0])
.then(calculateTotal);

Este codigo lo podemos entender rapidamente debido a la familiaridad. Pero el flujo del codigo es por todo el lugar. Aqui lo que se hace es: Calcular el total de las promociones del primer producto del carrito de compras.

Mira el orden de esta ultima frase, y compara el orden del codigo anterior con la siguiente funcion:

1
2
3
4
5
6
// composeP es una funcion compose que acepta promesas
var calculateTotalPromotions = composeP(
calculateTotal,
getPromotions,
head, // head retorna el primer elemento.
prop('products'));

Leer el codigo y entender lo que va a ocurrir es mucho mas sencillo! :)

Conclusion

PointFree programming tiene que ver con la modularizacion de funciones a trabes de la composicion. El uso de funciones mas pequeñas, genericas, bien definidas y bien probadas para construir las funciones que necesitamos. Dentro de compose o composeP, una funcion es llamada, luego la otra, y esto es lo que pasa. No importa si la funcion tiene 17 lineas o 3. Esto funciona siempre de la misma manera, y la complejidad del codigo no se incrementa.

Tampoco tenemos porque preocuparnos acerca de las variables temporales, lo que hace que sea mucho mas facil de entender el codigo y mas dificil introducir errores. Ademas, es mas facil de entender y poner a prueba las partes mas pequeñas del codigo, lo que hace que sea mas fiable.

Por supuesto que no es una bala de plata, y existen una gran cantidad de veces que uno termina escribiendo una funcion que no es totalmente pointfree. Me parece que su principal debilidad se produce cuando la funcion tiene mas de un parametro; muy a menudo se llega a tener un codigo no pointfree. Pero, dicho esto, mi experiencia personal es que la parte pointfree del codigo es mucho mas robusta y los errores son identificados facilmente durante la falla de desarrollo. Los errores casi nunca se encuentran en codigo pointfree!!.

Comentarios