JavaScript — Closures & Currying
Temario
- Introducción
- ¿Qué son las closures y cómo funcionan?
- ¿Qué es el currying y cómo funciona?
i. Introducción
Cuando empezamos a trabajar con JS es muy común escuchar la palabra closure, y es TAAAAN común escucharla que es increíble que muy pocas personas entiendan cómo se utiliza de manera correcta… pero… entonces por que si es tan popular muy pocos la aplican de manera correcta… quizás somos malos explicando o entendemos mal el concepto, continúa leyendo y te darás cuenta que es más sencillo de lo que suena…
i. ¿Qué son las closures y cómo funcionan?
Antes de empezar repasemos, ya que lo que hemos visto anteriormente nos ha preparando para este momento…
Recordemos que para la variable declarada con var su alcance es function scope, así como let y const que su alcance es block scope, veamos el siguiente ejemplo:
Podemos observar que tenemos dos bloques, donde el bloque interno puede ver sus variables (b) y las variables externas (a), y el bloque externo puede ver sólo sus propias variables (a).
Si vemos nuestro ejemplo en memoria, quedaría algo así:
La variable a persiste en memoria en su bloque, como en el bloque interno, y la variable b sólo persiste en su propio bloque.
Y recordemos que aplica la misma lógica para las funciones (function scope):
Tenemos dos bloques, donde el bloque interno (función bar) puede ver sus variables (b) y las variables externas (a), y el bloque externo (función foo) que solo puede ver sus propias variables (a).
Cuando anidamos una función dentro de otra función, pasa lo siguiente:
Veamos el siguiente ejemplo:
De acuerdo a lo que hemos venido explicando, la función increment crea las variables internas (local) y “encierra” las variables externas (counter):
Dentro de nuestra variable incrementCounter estamos creando un nuevo ámbito de nuestra función increment, donde a su vez persiste todas las variables “encerradas” (counter); entonces cuando ejecutamos nuestra función (incrementCounter) no va a afectar la persistencia de las variables externas.
Entonces cuando nosotros hacemos closures, realmente estamos creando funciones con persistencia (conservar en memoria) de variables externas, donde no podemos acceder a ellas de manera explícita y así las protegemos contra cambios no deseados; y no simplemente poner una función dentro de otra función.
Los closures se ocupan mucho para encapsular código y generar variables y métodos privados (Spoiler: Es un patrón de diseño “module pattern”) sin necesidad de crear de manera explícita una clase. Veamos el siguiente ejemplo:
Imaginemos que tenemos una función zombie, que va a tener algunos métodos privados (changeSpeed) y otros públicos (speedUp, speedDown, getSpeed), así como variables privadas (speed)
Podemos observar en nuestro ejemplo que tenemos dos variables diferentes zombieMau y zombieBenja (haciendo la instancia de la función zombie en cada una), la ventaja es que cada uno se comporta de manera independiente (haciendo referencia a variables y métodos diferentes).
Entonces para poder lograr una closure debe cumplir con los siguientes puntos:
- Que tenga un alcance léxico [ref].
- Que acepte funciones anidadas [ref].
- Y que acepte funciones como valores de primera clase.
Y esto es aún más interesante… el primer lenguaje que logró ejecutar esta idea fue JavaScript… ya después C#, Python, Java, PHP lo adoptaron.
Veamos un ejemplo utilizando una función anónima:
Podemos observar que tenemos una función anónima autoejecutable, donde internamente tiene una lista (list) y un retorno de una función, donde recibe como parámetro un número, y esta nos va a retornar un valor de la lista (list); de esta forma nos aseguramos que la lista no pueda ser modificada de manera externa y además generamos un scope limitado.
Por ahora es todo lo que vamos a ver de closures, pero… cuando hablemos de programación funcional** lo llevaremos a otro nivel…
ii. ¿Qué es el currying y cómo funciona?
Currying es el proceso de transformar una función que tiene múltiples argumentos en múltiples funciones de un solo argumento.
El término de Curry es en honor al matemático y lógico Haskell Brooks Curry [ref] que contribuyó de manera activa a los lenguajes funcionales, actualmente existen 3 lenguajes de programación que llevan su nombre: Haskell, Brooks y Curry [ref], así que ya sabes que no es por la comida [ref].
Antes de seguir explicando quiero aclarar que de manera natural JavaScript NO soporta currying, veamos por qué…
De acuerdo a la definición de arriba, nos dice que aplica a una función que tiene múltiples argumentos… entonces tenemos una función sum que soporta 3 argumentos (a, b, c)
Hasta aquí no hay ningún problema, después nos dice que esa función se transforma en múltiples funciones, donde cada una acepte un valor, entonces, nosotros esperamos que lo siguiente funcione:
Al probar el código anterior nos va a arrojar un error diciendo que sum no es una función…
Hagamos el ejemplo paso a paso:
Y es por eso que JavaScript se dice que no soporta de manera nativa el currying, pero podemos adaptar el concepto y hacer que funcione como currying, para eso vamos a usar las closures, bien…hagamos unos pequeños cambios a nuestro código:
Lo que hicimos fue anidar 3 funciones para cada uno de los valores (a, b, c) y con eso ya cumplimos la segunda parte del significado de curry (múltiples funciones donde cada una acepta un valor y esta a su vez retorna otra función o un valor).
Reto: La idea es que pases la función sum a función de flecha (arrow function)…
Respuesta:
La forma de hacer el curry en la parte de arriba recibe el nombre de currying estricto; también podemos invocar las funciones a nuestra conveniencia:
En el ejemplo anterior se dice que es currying por conveniencia o parcial, ya que vamos invocando cada una de las funciones de manera separada; esto es útil cuando uno de los parámetros a mandar viene de una API o de algún cálculo.
Creo que hasta aquí ya cumplimos con el concepto de currying, pero… que pasaría ahora si invocamos nuestra función de la manera tradicional:
Al imprimir en consola la variable sumOfThreeNumbers nos regresa la función y no la suma de los 3 números; la idea es que nosotros podamos hacer currying a una función y poder usarla de acuerdo a nuestras necesidades, ejemplo:
En el ejemplo anterior se espera que si ejecutamos nuestra función de manera tradicional o como una curry funcione…
Lo que vamos a hacer es convertir una función tradicional a una curry dinámica… por fortuna existen bibliotecas que ya lo hacen: lodash[ref], ramda [ref], o inclusive puedes hacer la tuya [ref]…
Para nuestro ejemplo, vamos a utilizar la librería lodash (por ahora no vamos a hacer una personalizada ya que nos faltan algunos temas por ver, pero… cuando hablemos de programación funcional** ahí sí haremos una desde cero…
Hemos aplicado curry a nuestra función sum, ahora podemos invocar de manera tradicional, por currying estricto o currying por conveniencia…
Es muy importante entender currying ya que sin darnos cuenta estamos aplicando los conceptos de funciones de primera clase, de orden superior, funciones anidadas, closures, scoping, etc...
Cuando veamos loops [ref]vamos a realizar un ejemplo con currying; así como también cuando veamos eventos** veremos un ejemplo más real, útil y avanzado.
En la siguiente entrega vamos a ver JavaScript — Shallow copy and Deep copy
La entrega pasada vimos JavaScript — Hoisting
Bibliografía y links que te puede interesar…