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);
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))
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);
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)
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)
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)
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)
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?
