Programación Funcional

Jose Lopez
9 min readJul 21, 2017

--

Bien sabemos que JavaScript no es un lenguaje Funcional del todo, comparandolo con Haskell, Scala, Clojure, Erlang y algunos otros. Sin embargo, JavaScript tiene sus características que podemos adaptar el Paradigma de Programación Funcional.

Ahora, que es la Programación Funcional?

La Programación Funcional es un paradigma de programación, un estilo de construir la estructura y elementos de programas de computadores, que trata los cálculos como la evaluación de funciones matemáticas y evita el cambio de estado y mutar la estructura de datos, se puede decir que también es un Paradigma de Programación Declarativa ya que depende de las expresiones o declaraciones en vez de estados. En la Programación Funcional el valor de la salida de una función depende solo en los argumentos que son la entrada a la función, así eliminando efectos secundarios, cambios en el estado que no dependen en las entradas de la función, mas legible para entender y ver el comportamiento del programa.

En este Post tocare los temas mas importantes de la FP, si ya has trabajado con lenguajes funcionales ya estas muy consciente, de lo contrario terminaras entendiendo los conceptos claves de la FP.

Funciones de Primera Clase

Cuando decimos Funciones de Primera Clase no hablamos de una Clase como tal, nos referimos al tipo de dato, por ejemplo: un número o una cadena de carácteres puede ser una Función de Primera Clase. Esto significa que podemos tratar Funciones como cualquier tipo de dato (en vez de tomar un número o cadena), no existe ningún método, palabra clave o algo especial para definirlas, en este caso decimos que las funciones se pueden asignar en una variable, objecto o incluso un vector. Veamos unos simples ejemplos en código.

un número se puede asignar a una variable y así asignamos una función retornando un número:

un número se puede asignar a un espacio en un vector y así una función:

un número puede ser asignado a un campo de un objeto y así una función:

un número puede ser creado como sea necesario y también una función:

un número puede ser pasado a una función y así puede hacerlo una función:

un número puede ser retornado desde una función y así puede hacerlo una función:

Como se ve en los ejemplos las Funciones de Primera Clase no tienen nada de especial, solo las tomamos como cualquier otro tipo de dato pero en este caso es una función retornando un valor. Los últimos dos ejemplos le llamamos Funciones de Orden Superior que es el siguiente tema a tratar.

Funciones de Orden Superior

En esta capítulo del Post vamos a extender nuestra idea de Funciones de Primera Clase, aqui voy a describir que las funciones no solo puede ser asignadas a una variable, objeto, vector o pasadas como cualquier tipo de dato, ellas también pueden ser retornadas desde otras funciones. Las Funciones de Orden Superior toman dos acciones claves: toman una función como un argumento y retorna una función como un resultado, fundamental para el paradigma de Programación Funcional, ahora ya estas listo para ver la explicación en código!

Vamos a desglosar este tópico en un programa muy sencillo para su buen entendimiento del mismo, vamos allá!

Bien, ahora vamos a dividir esta explicación en partes:

Primero he asignado una función a una variable llamada add como si fuese un tipo de dato (Funciones de Primera Clase que vimos anteriormente), este tipo de funciones son también conocidas como Funciones Anónimas o algunos lenguajes de programación se les conoce como Lambda, esta función anónima toma un argumento.

Luego dentro del ámbito (Es decir, es un closure) de add retornamos otra función anónima con su argumento y así tomando el argumento del closure y la de esta para retornar una suma de los dos argumentos.

Para llamar la función debemos asignarla a una variable con su respectivo parámetro a la primera función (la asignamos a una variable porque no queremos que se ejecute, si solo ejecutamos la primera función no va a devolver el resultado), luego ejecutamos la segunda función asignandole su respectivo parámetro y listo nos devuelve el resultado.

Es algo tedioso llamar las Funciones de Orden Superior (te puedes imaginar si tienes muchas más funciones que este simple ejemplo? xD), para esto usamos una técnica llamada Currying muy usada en el paradigma de Programación Funcional. Pero que diablos es Currying?

Currying

Currying es una forma de construir funciones por partes mientras que permita la aplicación parcial de los argumentos de una función.

Es decir podriamos pasar todo los argumentos de una función en partes y ejecutarla de una vez, muy simple y así reduce la complejidad en el código, vamos a ejecutar el ejemplo de arriba usando Currying, veamos!

Muy simple! así solo tenemos que llamar la función una sola vez pasandole los argumentos o resto de elementos a las funciones en parcial. En lenguajes como Haskell, Scala, ya vienen por default con esta feature ya que son lenguajes funcionales totalmente, en cambio en JavaScript no viene por default, la técnica de Currying se aplica como un tipo de truco o hack para hacerlo trabajar como Currying.

Sin embargo podemos usar alguna librería Open Source que nos facilite mucho la vida para estos procesos funcionales, voy a tomar el mismo ejemplo pero usando Lodash, vamos alla!

El método curry de Lodash toma una función como argumento (un callback) transformando los argumentos de este callback en una función Currying.

Map, Reduce y Filter

Pues si, JavaScript tiene funciones pre-construidas (a partir de ES5) que toman una función como argumento y retornan un valor y esas funciones son map(), reduce() y filter() para vectores, veamos un ejemplo de cada una tomando esta simple estructuras de datos:

con filter()

con map()

con reduce()

Encadenando métodos

Como vemos map(), reduce() y filter() son Funciones de Orden Superior que vienen en el core de JavaScript, estas funciones toman datos de un vector pasandolo al callback y retornan un valor transformado sin mutar el vector original, luego veremos en profundida la parte de las Funciones Puras e Immutabilidad, la próxima parada es para la Composición de Funciones.

Composición

Yo pienso que Componer Funciones es un arte, pero realmente en un contexto en general, que es Composición?

Formación de un todo o un conjunto unificado uniendo con cierto orden una serie de elementos. por Google.

Así es, Los elementos serian las funciones que van transformando las entradas, ya sabemos que una función puede ser declarada como cualquier otro tipo de dato y que las funciones pueden retornar otras funciones, pero no como pueden ser compuestas para transformar datos, ahora veamos un simple ejemplo:

f y g son funciones y x el valor para ser transformado a través de ellas.

La composición de dos funciones retorna una nueva función, componer dos unidades de un tipo de dato en este caso una función, deberia retornar una nueva unidad de ese tipo, similar a map(), reduce() y filter() solo que estas funciones heredan del Array.prototype y retornan es un nuevo vector.

Componer funciones es como construir un juego de Lego, combinas dos pieza (dos funciones) y tienes una pieza completa (retorna una nueva función).

Ahora vamos a llamar compose y veamos los resultados:

En la función compose, el argumento g se ejecuta antes que f, creando un flujo de datos de derecha a izquierda, así es mucho más legible que anidar varias llamadas de funciones, sin compose, debería ser así:

Trabaja igual, pero como ves el flujo cambia, en vez de ser de adentro hacia fuera compose lo hace de derecha a izquierda, la secuencia también importa, composición viene muy fuerte de las Matemáticas, Teoría de las Categorías para ser especifico (tranquilo, no tocare este tema en profundidad, solo ejemplos con código), vamos que a ver algunas propiedades de la Composición:

La Composición es asociativa, es decir que no importa como se agrupen las propiedades, ahora vamos a probar con un ejemplo:

Como visualizamos en el ejemplo de arriba, no importa como nosotros agrupamos nuestra llamada a compose, el resultado puede ser el mismo, así podemos componer varias funciones.

Pureza

Una función pura es una función que dada la misma entrada puede siempre retornar la misma salida, no produce efectos sedundarios y no confia en estados externos. Muy buenas razones para usar funciones puras y parte de la Programación Funcional, suelen ser muy simples, reusables y muy fácil para testear, manteniendo el principio de diseño KISS (Keep It Simple, Stupid). Las funciones puras son totalmente independientes, no dependen de estados externos del programa, así evitando bugs y manteniendo el código mas flexible y ordenado.

Ahora vamos a ver un ejemplo de una simple función pura que dada la misma entrada retorna la misma salida:

Así de simple es una función pura, toma una entrada (argumento) y retorna una salida (retornando el mismo resultado), sin modificar estados externos, sin efectos secundarios, ahora vamos a tomar este mismo ejemplo para una función impura.

En este caso la función double() toma una variable global, asignandole dentro de su contexto el argumento y así modificando su valor, es decir double() depende de una variable mutable (modificando estados externos como he comentado), a las funciones puras no les agrada modificar estados externos, y debemos tenerlo en cuenta para evitar bugs.

Vamos hacer lo mismo para mostrar un ejemplo de una función con efectos secundarios:

Un efecto secundario es cambiar el estado o interación global que ocurre durante el cálculo de un resultado.

En este caso la función double() tiene en su contexto la función getId() que toma como argumento el resultado, como vemos double() depende de getId(), esto produce efectos secundarios ya que el resultado lo toma otra función y mutar datos, getId() problamente sea una petición http (Ajax) obteniendo un id, otros efectos secundarios serian como: insertar datos en una base de datos, manipular el DOM, cambiar el sistema de archivos o simplemente mutar estados globales del programa.

Las funciones puras solo confían en estados immutables, para el próximo paso vamos a tocar este tema de Immutabilidad.

Immutabilidad

Como hemos visto anteriormente, las funciones puras no confían en mutar datos (modificar el estado del programa), cuando tenemos una función que modifica una propiedad en un objeto o vector estamos modificando el estado desde el contexto global, las funciones puras no deben mutar el estado externo del programa. Veamos los ejemplos:

addPleople() es una función que toma dos argumentos, un vector pleopleData y un objeto person, la función trabaja sin problemas, agrega una persona a los datos de personas, pero el problema es que estamos mutando el estado que compartimos en la función, addPleople() depende de peopleData (recuerda que las funciones puras son independientes) y modificando su estado original, para resolver este problema podriamos clonar los datos antes de enviarlos.

Voy a usar lodash para clonar los datos, veamos esta versión del ejemplo:

Como hemos visto en este ejemplo, ahora tomamos los datos y lo clonamos, luego enviamos el objeto y retornamos el resultado, así evitamos mutar el estado original de la aplicación y ademas en el contexto de la función siendo addPleople() una función totalmente independiente.

Memoización

Como hemos mencionado las funciones puras toman entradas que se pueden repetir muchas veces, esas entradas pueden ser cacheadas, para esto usamos una técnica llamada Memoización que es una técnica de optimización para almacenar resultados de llamadas a funciones y retorna el resultado cacheado cuando la misma entrada ocurre de nuevo.

Vamos a implementar un ejemplo usando Memoización:

Si repetimos la misma entrada nos retorna el mismo resultado pero ya desde el “cache”.

Recursión

La Programación Funcional esta muy relacionada con la Recursividad. La Recursión básicamente es que una función pueda llamarse así misma para resolver una instancia de un problema. Recursión suele aplicarse cuando necesitamos llamar la misma función repetidamente con diferentes parámetros desde un bucle. Podemos resolver diferentes problemas, el más común es iterar sobre una estructura de datos hasta encontrar, calcular o ordenar y obtener el resultado. Las funciones recursivas son puras, ya que no comparte el estado con variables locales, así siendo muy faciles de testear. Retornan un valor para cualquier entrada sin efectos secundarios en variables globales.

Veamos un típico ejemplo de una función factorial:

factorial() es una función recursiva donde toma un argumento, en este caso es un número y si este número es menor o igual a 0 retorna 1 de lo contrario retorna el número multiplicado por el factorial, como vemos llamamos la función así misma para retornar el resultado final. Y así trabajan las funciones recursivas, como un bucle o condicional, de hecho en algunos lenguajes que no tienen condicionales o bucles por default suelen usar recursividad.

Conclusión

El paradigma de Programación Funcional es mucho más complejo y extenso, pero no es la idea hacerlo mas complejo y extenso, este post te puede dar un conocimiento en general de la Programación Funcional, se consciente de que JavaScript no es un lenguaje 100% funcional, sin embargo tiene sus características funcionales que las debemos aprovechar al máximo, si usas lenguajes funcionales ya esto sera muy fácil para ti. Lo más cool es que con JavaScript podemos combinar Programación Orientada a Objetos y Funcionaldependiendo de nuestras necesidades o del problema que nos toque resolver. Y así concluyó este post sobre Programación Funcional en JavaScript, espero que te haya gustado y recibido toda esta info para aplicarla en tus programas.

--

--