Funções de Currying JavaScript

A programação funcional é um estilo de desenvolvimento de programa, no qual alguns recursos específicos para trabalhar com funções são amplamente utilizados. Trata-se, em particular, de transferir funções para outras funções como argumentos e de retornar funções de outras funções. O conceito de "funções puras" também pertence ao estilo funcional de programação. A saída de funções puras depende apenas da entrada; elas, quando executadas, não afetam o estado do programa.

Os princípios de programação funcional são suportados por muitos idiomas. Entre eles estão JavaScript, Haskell, Clojure, Erlang. O uso de mecanismos de programação funcional implica conhecimento, entre outros, de conceitos como funções puras, funções de curry, funções de ordem superior.



O material que estamos traduzindo hoje é sobre curry. Falaremos sobre como o curry funciona e como o conhecimento desse mecanismo pode ser útil para um desenvolvedor de JS.

O que é curry?


Currying em programação funcional é a transformação de uma função com muitos argumentos em um conjunto de funções aninhadas com um argumento. Quando uma função ao curry é chamada com um argumento passado, ela retorna uma nova função que espera que o próximo argumento chegue. Novas funções que aguardam o próximo argumento são retornadas toda vez que a função ao curry é chamada - até que a função receba todos os argumentos de que precisa. Os argumentos recebidos anteriormente, graças ao mecanismo de fechamento, aguardam o momento em que a função obtém tudo o que precisa para realizar os cálculos. Após receber o último argumento, a função executa o cálculo e retorna o resultado.

Falando em curry , podemos dizer que esse é o processo de transformar uma função com vários argumentos em uma função com menos aridade.

Arity é o número de argumentos para uma função. Por exemplo, aqui está a declaração de um par de funções:

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

A função fn recebe dois argumentos (esta é uma função binária ou de 2 árias), a função _fn recebe três argumentos (uma função ternária de 3 árias).

Vamos falar sobre a situação em que, durante o currying, uma função com vários argumentos é convertida em um conjunto de funções, cada uma das quais recebe um argumento.

Considere um exemplo. Temos a seguinte função:

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

Leva três argumentos e retorna seu produto:

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

Agora vamos pensar em como convertê-lo em um conjunto de funções, cada uma das quais recebe um argumento. Vamos criar uma versão atualizada desta função e ver como obter o mesmo resultado ao chamar várias funções:

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

Como você pode ver, aqui convertemos a chamada em uma única função com três argumentos - multiply(1,2,3) na chamada em três funções - multiply(1)(2)(3) .

Acontece que uma função se transformou em várias funções. Ao usar a nova construção, cada função, exceto a última, retornando o resultado dos cálculos, pega um argumento e retorna outra função, também capaz de aceitar um argumento e retornar outra função. Se uma construção do formulário multiply(1)(2)(3) não lhe parecer muito clara, vamos escrevê-lo neste formulário para entender melhor isso:

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

Agora vamos alinhar por linha o que está acontecendo aqui.

Primeiro, passamos o argumento 1 para a função multiply :

 const mul1 = multiply(1); 

Quando essa função funciona, esse design funciona:

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

Agora mul1 tem uma referência a uma função que aceita um argumento b . Chamamos a função mul1 , passando 2 :

 const mul2 = mul1(2); 

Como resultado dessa chamada, o seguinte código será executado:

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

A mul2 conterá uma referência a uma função que poderia estar nela, por exemplo, como resultado da seguinte operação:

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

Se agora chamarmos a função mul2 , passando-a 3 , a função executará os cálculos necessários usando os argumentos b :

 const result = mul2(3); 

O resultado desses cálculos será 6 :

 log(result); // 6 

A função mul2 , que possui o nível mais alto de aninhamento, tem acesso ao escopo, aos fechamentos formados pelas mul1 multiply e mul1 . É por isso que na função mul2 possível executar cálculos com variáveis ​​declaradas em funções cuja execução já foi concluída, que já retornou alguns valores e é processada pelo coletor de lixo.

Acima, examinamos um exemplo abstrato, mas, em essência, a mesma função, projetada para calcular o volume de uma caixa retangular.

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

Aqui está a aparência de sua versão ao curry:

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

Portanto, o curry é baseado na seguinte idéia: com base em uma determinada função, é criada outra função que retorna uma função especializada.

Caril e uso parcial de funções


Agora, talvez, haja a sensação de que o número de funções aninhadas, ao representar uma função como um conjunto de funções aninhadas, depende do número de argumentos para a função. E se se trata de curry, então é.

Uma versão especial da função para calcular o volume, que já vimos, pode ser feita da seguinte maneira:

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

Aqui as idéias são aplicadas, muito semelhantes às discutidas acima. Você pode usar esta função da seguinte maneira:

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

E você pode fazer isso:

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

De fato, aqui você pode ver como nós, com o comando volume(70) , criamos uma função especializada para calcular o volume de corpos, uma das dimensões das quais (a saber, comprimento, l ) é fixa. A função de volume espera 3 argumentos e contém 2 funções aninhadas, ao contrário da versão anterior de uma função semelhante, cuja versão ao curry continha 3 funções aninhadas.

A função que foi obtida após chamar o volume(70) implementa o conceito de um aplicativo de função parcial. O curry e a aplicação parcial de funções são muito semelhantes entre si, mas os conceitos são diferentes.

Em aplicação parcial, a função é transformada em outra função com menos argumentos (menos aridade). Alguns argumentos dessa função são fixos (os valores padrão são definidos para eles).

Por exemplo, existe uma função:

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

Pode ser convertido para isso:

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

A implementação da função performOp() não é fornecida aqui, pois não afeta os conceitos em consideração.

A função que pode ser obtida chamando a nova função acidityRatio() com um argumento cujo valor precisa ser corrigido é a função original, um dos argumentos que é fixo, e essa função em si leva um argumento a menos que o original.

A versão ao curry da função terá a seguinte aparência:

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

Como você pode ver, ao currying, o número de funções aninhadas é igual ao número de argumentos da função original. Cada uma dessas funções espera seu próprio argumento. É claro que, se a função dos argumentos não aceitar ou aceitar apenas um argumento, não poderá ser alterada.

Em uma situação em que uma função tem dois argumentos, pode-se dizer que os resultados de sua aplicação parcial e curry coincidem. Por exemplo, temos uma função:

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

Suponha que precisamos reescrevê-lo para que possamos, corrigindo o primeiro argumento, obter uma função que execute cálculos ao passar apenas o segundo argumento, ou seja, precisamos aplicar parcialmente essa função. Ficará assim:

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

O resultado da curry será exatamente o mesmo.

Sobre a aplicação prática dos conceitos de curry e aplicação parcial de funções


O curry e a aplicação parcial de funções podem ser úteis em várias situações. Por exemplo, ao desenvolver pequenos módulos adequados para reutilização.

O uso parcial de funções facilita o uso de módulos universais. Por exemplo, temos uma loja online, no código da qual existe uma função usada para calcular o valor a ser pago, levando em consideração o desconto.

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

Existe uma certa categoria de clientes, vamos chamá-los de "clientes amados", a quem damos um desconto de 10%. Por exemplo, se esse cliente compra algo por US $ 500, oferecemos a ele um desconto de US $ 50:

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

É fácil perceber que, com essa abordagem, precisamos constantemente chamar essa função com dois 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 

A função original pode ser reduzida para um formulário que permita o recebimento de novas funções com um nível de desconto predeterminado, mediante a chamada da qual é suficiente transferir o valor da compra. A função discount() em nosso exemplo possui dois argumentos. Aqui está como é que convertemos:

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

A função tenPercentDiscount() é o resultado da aplicação parcial da função discount() . Ao chamar tenPercentDiscount() dessa função, basta passar o preço e um desconto de 10%, ou seja, o argumento de discount , já estará definido:

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

Se houver compradores em nossa loja que decidiram dar um desconto de 20%, você poderá obter a função apropriada para trabalhar com eles assim:

 const twentyPercentDiscount = discount(0.2); 

Agora, a função twentyPercentDiscount() pode ser chamada para calcular o custo das mercadorias, levando em consideração um desconto de 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 

Função universal para aplicação parcial de outras funções


Desenvolveremos uma função que aceita qualquer função e retorna sua variante, que é uma função, alguns dos argumentos já definidos. Aqui está o código que permite que você faça isso (você, se pretender desenvolver uma função semelhante, é bem possível que você obtenha outra coisa como resultado):

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

A função partial() aceita a função fn , que queremos converter na função parcialmente aplicada, e um número variável de parâmetros (...args ). A instrução rest é usada para colocar todos os parâmetros após fn em args .

Esta função retorna outra função que também aceita um número variável de parâmetros ( _arg ). Esta função, por sua vez, chama a função fn original, transmite os parâmetros ...args e ..._arg (usando o operador spread ). A função executa o cálculo e retorna o resultado.

Usamos esta função para criar uma variante da função de volume já familiar para você, projetada para calcular o volume de paralelepípedos retangulares, um de cujos lados é fixo:

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

Aqui você pode encontrar um exemplo de função universal para currying outras funções.

Sumário


Neste artigo, falamos sobre curry e aplicação parcial de funções. Esses métodos para transformar funções são implementados em JavaScript devido ao fechamento e ao fato de as funções em JS serem objetos da primeira classe (elas podem ser passadas como argumentos para outras funções, retornadas por elas, atribuídas a variáveis).

Caros leitores! Você usa técnicas de curry e aplicação parcial de funções em seus projetos?

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


All Articles