Funciones de currículum de JavaScript

La programación funcional es un estilo de desarrollo de programas en el que se utilizan ampliamente algunas características específicas para trabajar con funciones. Se trata, en particular, de transferir funciones a otras funciones como argumentos y de devolver funciones de otras funciones. El concepto de "funciones puras" también pertenece al estilo funcional de programación. La salida de las funciones puras depende solo de la entrada, ellas, cuando se ejecutan, no afectan el estado del programa.

Los principios de programación funcional son compatibles con muchos lenguajes. Entre ellos están JavaScript, Haskell, Clojure, Erlang. El uso de mecanismos de programación funcional implica el conocimiento, entre otros, de conceptos tales como funciones puras, funciones de curry, funciones de orden superior.



El material que estamos traduciendo hoy trata sobre curry. Hablaremos sobre cómo funciona el curry y cómo el conocimiento de este mecanismo puede ser útil para un desarrollador de JS.

¿Qué es curry?


Curry en la programación funcional es la transformación de una función con muchos argumentos en un conjunto de funciones anidadas con un argumento. Cuando se llama a una función curry con un argumento pasado, devuelve una nueva función que espera que llegue el siguiente argumento. Las funciones nuevas que esperan el siguiente argumento se devuelven cada vez que se llama a la función currificada, hasta que la función recibe todos los argumentos que necesita. Los argumentos obtenidos anteriormente, gracias al mecanismo de cierre, esperan el momento en que la función obtiene todo lo que necesita para realizar los cálculos. Después de recibir el último argumento, la función realiza el cálculo y devuelve el resultado.

Hablando de curry , podemos decir que este es el proceso de transformar una función con varios argumentos en una función con menos arity.

Arity es el número de argumentos de una función. Por ejemplo, aquí está la declaración de un par de funciones:

function fn(a, b) {    //... } function _fn(a, b, c) {    //... } 

La función fn toma dos argumentos (esta es una función binaria o de 2 arios), la función _fn toma tres argumentos (una función ternaria de 3 arios).

Hablemos de la situación cuando, durante el currículum, una función con varios argumentos se convierte en un conjunto de funciones, cada una de las cuales toma un argumento.

Considera un ejemplo. Tenemos la siguiente función:

 function multiply(a, b, c) {   return a * b * c; } 

Toma tres argumentos y devuelve su producto:

 multiply(1,2,3); // 6 

Ahora pensemos en cómo convertirlo en un conjunto de funciones, cada una de las cuales requiere un argumento. Creemos una versión currificada de esta función y veamos cómo obtener el mismo resultado al llamar a varias funciones:

 function multiply(a) {   return (b) => {       return (c) => {           return a * b * c       }   } } log(multiply(1)(2)(3)) // 6 

Como puede ver, aquí convertimos la llamada a una sola función con tres argumentos: multiply(1,2,3) a la llamada a tres funciones: multiply(1)(2)(3) .

Resulta que una función se ha convertido en varias funciones. Cuando se utiliza la nueva construcción, cada función, excepto la última, que devuelve el resultado de los cálculos, toma un argumento y devuelve otra función, también capaz de aceptar un argumento y devolver otra función. Si una construcción de la forma multiply(1)(2)(3) no le parece muy clara, escríbala en esta forma para comprender mejor esto:

 const mul1 = multiply(1); const mul2 = mul1(2); const result = mul2(3); log(result); // 6 

Ahora, línea por línea, lo que está sucediendo aquí.

Primero, pasamos el argumento 1 a la función de multiply :

 const mul1 = multiply(1); 

Cuando esta función funciona, este diseño funciona:

 return (b) => {       return (c) => {           return a * b * c       }   } 

Ahora mul1 tiene una referencia a una función que toma un argumento b . Llamamos a la función mul1 , pasándola 2 :

 const mul2 = mul1(2); 

Como resultado de esta llamada, se ejecutará el siguiente código:

 return (c) => {           return a * b * c       } 

La mul2 contendrá una referencia a una función que podría estar en ella, por ejemplo, como resultado de la siguiente operación:

 mul2 = (c) => {           return a * b * c       } 

Si ahora llamamos a la función mul2 , pasándola 3 , entonces la función realizará los cálculos necesarios utilizando los argumentos a y b :

 const result = mul2(3); 

El resultado de estos cálculos será 6 :

 log(result); // 6 

La función mul2 , que tiene el nivel más alto de anidamiento, tiene acceso al alcance, a los cierres formados por las mul1 multiply y mul1 . Es por eso que en la función mul2 posible realizar cálculos con variables declaradas en funciones cuya ejecución ya se ha completado, que ya han devuelto algunos valores y son procesados ​​por el recolector de basura.

Arriba examinamos un ejemplo abstracto, pero, en esencia, la misma función, que está diseñada para calcular el volumen de una caja rectangular.

 function volume(l,w,h) {   return l * w * h; } const vol = volume(100,20,90) // 180000 

Así es como se ve su versión al curry:

 function volume(l) {   return (w) => {       return (h) => {           return l * w * h       }   } } const vol = volume(100)(20)(90) // 180000 

Entonces, el curry se basa en la siguiente idea: sobre la base de una determinada función, se crea otra función que devuelve una función especializada.

Curry y uso parcial de funciones


Ahora, quizás, existe la sensación de que el número de funciones anidadas, cuando se representa una función como un conjunto de funciones anidadas, depende del número de argumentos de la función. Y si se trata de curry, entonces lo es.

Una versión especial de la función para calcular el volumen, que ya hemos visto, se puede hacer de la siguiente manera:

 function volume(l) {   return (w, h) => {       return l * w * h   } } 

Aquí se aplican ideas, muy similares a las discutidas anteriormente. Puede usar esta función de la siguiente manera:

 const hV = volume(70); hV(203,142); hV(220,122); hV(120,123); 

Y puedes hacer esto:

 volume(70)(90,30); volume(70)(390,320); volume(70)(940,340); 

De hecho, aquí puede ver cómo nosotros, con el comando volume(70) , creamos una función especializada para calcular el volumen de cuerpos, una de cuyas dimensiones (es decir, longitud, l ) es fija. La función de volume espera 3 argumentos y contiene 2 funciones anidadas, a diferencia de la versión anterior de una función similar, cuya versión al curry contenía 3 funciones anidadas.

La función que se obtuvo después de llamar al volume(70) implementa el concepto de una aplicación de función parcial. El currículum y la aplicación parcial de funciones son muy similares entre sí, pero los conceptos son diferentes.

En una aplicación parcial, la función se transforma en otra función con menos argumentos (menos aridad). Algunos argumentos de dicha función son fijos (se establecen valores predeterminados para ellos).

Por ejemplo, existe tal función:

 function acidityRatio(x, y, z) {   return performOp(x,y,z) } 

Se puede convertir a esto:

 function acidityRatio(x) {   return (y,z) => {       return performOp(x,y,z)   } } 

La implementación de la función performOp() no se proporciona aquí, ya que no afecta los conceptos bajo consideración.

La función que se puede obtener llamando a la nueva función acidityRatio() con un argumento cuyo valor necesita ser arreglado es la función original, uno de los argumentos de los cuales es fijo, y esta función en sí misma toma un argumento menos que el original.

La versión curry de la función se verá así:

 function acidityRatio(x) {   return (y) = > {       return (z) = > {           return performOp(x,y,z)       }   } } 

Como puede ver, al cursar, el número de funciones anidadas es igual al número de argumentos de la función original. Cada una de estas funciones espera su propio argumento. Está claro que si la función de los argumentos no acepta, o acepta solo un argumento, entonces no se puede cursar.

En una situación en la que una función tiene dos argumentos, se puede decir que los resultados de su currículum y su aplicación parcial coinciden. Por ejemplo, tenemos tal función:

 function div(x,y) {   return x/y; } 

Supongamos que necesitamos reescribirlo para poder, arreglando el primer argumento, obtener una función que realice cálculos al pasarle solo el segundo argumento, es decir, necesitamos aplicar parcialmente esta función. Se verá así:

 function div(x) {   return (y) => {       return x/y;   } } 

El resultado de su curry se verá exactamente igual.

Sobre la aplicación práctica de los conceptos de currículum y aplicación parcial de funciones.


El curry y la aplicación parcial de funciones pueden ser útiles en diversas situaciones. Por ejemplo, al desarrollar módulos pequeños adecuados para su reutilización.

El uso parcial de las funciones facilita el uso de módulos universales. Por ejemplo, tenemos una tienda en línea, en cuyo código hay una función que se utiliza para calcular la cantidad a pagar teniendo en cuenta el descuento.

 function discount(price, discount) {   return price * discount } 

Hay una cierta categoría de clientes, llamémoslos "clientes queridos", a quienes les damos un 10% de descuento. Por ejemplo, si dicho cliente compra algo por $ 500, le damos un descuento de $ 50:

 const price = discount(500,0.10); // $50 // $500 - $50 = $450 

Es fácil notar que con este enfoque, constantemente tenemos que llamar a esta función con dos argumentos:

 const price = discount(1500,0.10); // $150 // $1,500 - $150 = $1,350 const price = discount(2000,0.10); // $200 // $2,000 - $200 = $1,800 const price = discount(50,0.10); // $5 // $50 - $5 = $45 const price = discount(5000,0.10); // $500 // $5,000 - $500 = $4,500 const price = discount(300,0.10); // $30 // $300 - $30 = $270 

La función original se puede reducir a un formulario que le permita recibir nuevas funciones con un nivel de descuento predeterminado, al llamar, que es suficiente para transferir el monto de la compra. La función discount() en nuestro ejemplo tiene dos argumentos. Así es como se ve cuando lo convertimos:

 function discount(discount) {   return (price) => {       return price * discount;   } } const tenPercentDiscount = discount(0.1); 

La función tenPercentDiscount() es el resultado de la aplicación parcial de la función discount() . Al llamar a tenPercentDiscount() esta función, es suficiente para pasar el precio, y ya se establecerá un descuento del 10%, es decir, el argumento de discount :

 tenPercentDiscount(500); // $50 // $500 - $50 = $450 

Si hay compradores en nuestra tienda que han decidido dar un descuento del 20%, puede obtener la función adecuada para trabajar con ellos de esta manera:

 const twentyPercentDiscount = discount(0.2); 

Ahora se puede llamar a la función twentyPercentDiscount() para calcular el costo de los bienes, teniendo en cuenta un descuento del 20%:

 twentyPercentDiscount(500); // 100 // $500 - $100 = $400 twentyPercentDiscount(5000); // 1000 // $5,000 - $1,000 = $4,000 twentyPercentDiscount(1000000); // 200000 // $1,000,000 - $200,000 = $600,000 

Función universal para la aplicación parcial de otras funciones.


Desarrollaremos una función que acepte cualquier función y devuelva su variante, que es una función, cuyos argumentos ya están establecidos. Aquí está el código que le permite hacer esto (usted, si se propone desarrollar una función similar, es muy posible que obtenga algo más como resultado):

 function partial(fn, ...args) {   return (..._arg) => {       return fn(...args, ..._arg);   } } 

La función partial() acepta la función fn , que queremos convertir a la función parcialmente aplicada, y un número variable de parámetros (...args ). La instrucción rest se usa para poner todos los parámetros después de fn en args .

Esta función devuelve otra función que también acepta un número variable de parámetros ( _arg ). Esta función, a su vez, llama a la función fn original, pasa los parámetros ...args y ..._arg (usando el operador de spread ). La función realiza el cálculo y devuelve el resultado.

Utilizamos esta función para crear una variante de la función de volume que ya conoce, diseñada para calcular el volumen de paralelepípedos rectangulares, uno de cuyos lados es fijo:

 function volume(l,h,w) {   return l * h * w } const hV = partial(volume,100); hV(200,900); // 18000000 hV(70,60); // 420000 

Aquí puede encontrar un ejemplo de una función universal para cursar otras funciones.

Resumen


En este artículo, hablamos sobre el currículum y la aplicación parcial de funciones. Estos métodos para transformar funciones se implementan en JavaScript debido a cierres y al hecho de que las funciones en JS son objetos de la primera clase (pueden pasarse como argumentos a otras funciones, devolverse de ellas, asignarse a variables).

Estimados lectores! ¿Utiliza técnicas de curry y aplicación parcial de funciones en sus proyectos?

Source: https://habr.com/ru/post/es427295/


All Articles