JS Funcional # 5: Aplicação Parcial, Currying

Krzysztof Czernek Segue 11 de jun · 8 min ler

Esta é uma quinta parte da série “Functional JS”. Vá para a parte anterior , ou o começo da série .

Foto de no

Introdução

Na desta série, discutimos encerramentos. Vimos que eles nos permitem ter funções que retornam outras funções que lembram variáveis do seu escopo externo.

Aludimos ao fato de que isso se tornará útil assim que aprendermos sobre a aplicação parcial e o currying . Agora é hora de mergulhar nisso.

A programação funcional é toda sobre a composição de funções e o uso de funcionalidades genéricas para criar uma funcionalidade mais especializada. Isso permite menos duplicação e melhor legibilidade (expressividade) do código.

A aplicação parcial e o currying são mecanismos que podemos usar precisamente para isso – para construir versões especializadas de funções, além de variações mais genéricas.

Vamos ver o que são, para que podem ser usados e qual é a diferença entre esses dois conceitos.

Sobre o que estamos conversando?

Lembre ? Se não, você pode querer voltar e revisar este tópico. As técnicas que discutiremos agora são como funções de ordem superior, mas levadas um passo adiante.

Tanto a aplicação parcial quanto o curry estão relacionados às maneiras como invocamos funções – especificamente, funções que possuem mais de um parâmetro. Eles nos permitem chamar essas funções fornecendo apenas alguns dos argumentos, deixando o resto “para mais tarde”.

Se isso é confuso, ou soa como um tipo de coisa “certo, mas é realmente útil” – espere! Vamos entrar nisso, mas primeiro, vamos dar uma olhada nos exemplos para entender o que estamos falando.

Aplicação parcial

Dê uma olhada na função getApiURL e na forma como a usamos em alguns lugares:

Como você pode ver, há uma duplicação bastante óbvia de código acontecendo aqui. getUserURL , getOrderURL e getProductURL são parecidos, mas não é tão óbvio como consertar isso. Vamos tentar:

Isso é melhor. O que fizemos aqui foi getUserURL parte comum de getUserURL , getOrderURL e getProductURL para a função getResourceURL . Se pensarmos sobre isso, a parte comum é sobre chamar getApiURL e passar http://localhost:3000 para ele como o primeiro argumento.

Dessa forma, usamos uma função getApiURL mais genérica para criar um getResourceURL mais especializado que é um wrapper. Seu trabalho é fornecer getApiURL com o primeiro argumento. Chamadas subseqüentes para getResourceURL não precisarão mais se preocupar com a parte http://localhost:3000 – é resolvido.

O que fizemos aqui foi que aplicamos parcialmente a função getApiURL e criamos uma função getResourceURL "wrapper" em torno dela.

Para facilitar o uso desse padrão, podemos usar uma função de utilidade de ordem superior – partial .

Tome um momento para entender como funciona partial .

Veja como fazemos uso de closures e temos argsToApply ainda disponível no momento em que chamamos fn(...argsToApply, ...restArgs) ?

Levando isso um passo adiante, poderíamos aplicar parcialmente getResourceURL para criar getUserURL e as outras variações:

Está começando a ficar legal!

Curiosamente, existe um método que pode ser usado apenas como partial e está embutido na própria linguagem! Você pode até tê-lo usado. Isso é chamado bind . Ele liga a função não apenas a this contexto (um caso de uso mais popular), mas também a seus primeiros argumentos. Vamos dar uma olhada:

Também podemos aplicar parcialmente uma função fornecendo mais de um argumento. Neste exemplo, isso poderia significar a introdução da duplicação que estávamos tentando evitar em primeiro lugar – mas, para completar, vamos ver como seria:

Curry

O currying é uma técnica um pouco semelhante à aplicação parcial . Também nos permite “consertar” alguns dos parâmetros da função e retornar uma função que “aceita” o resto.

A diferença é que, com o currying, fornecemos argumentos para uma função, um de cada vez .

Vamos ver um exemplo:

Podemos ver aqui que getApiURLCurried é uma função que aceita apenas um argumento. Em seguida, ele retorna uma função que aceita mais um argumento e faz a mesma coisa novamente.

Para usar a versão curry de getApiURL , precisamos fornecer argumentos um de cada vez: primeiro apiHostname , em seguida, resourceName para a função retornada e, finalmente, resourceId para uma outra função interna.

Podemos escrever funções curry mais sucintamente:

O interessante é que essa forma não é muito mais detalhada do que a versão original.

Existe um utilitário curry fornecido em várias bibliotecas, que transforma uma função regular em seu equivalente curry. Esta função de utilidade é um pouco mais complexa do que o utilitário partial acima, portanto, não estaremos cobrindo seus detalhes de implementação. Sinta-se à vontade para ler as seguintes fontes para entender:

Veja como você pode usar o utilitário curry (aqui, da ):

Qual a diferença, de novo?

O currying e a aplicação parcial são padrões que nos permitem chamar funções com alguns de seus parâmetros e fornecer o restante posteriormente.

A diferença é que:

  1. A aplicação parcial é mais ou menos um padrão de chamar uma função . Você pode aplicar parcialmente qualquer função.
    O currying é mais uma forma da função . Para poder usar curry, você precisa criar explicitamente uma nova função que é uma versão curry da original.
  2. Digamos que uma função foo aceita argumentos N = 5 . O aplicativo parcial nos permite chamá-lo com argumentos K e recuperar uma função que aceita argumentos N - K . Por exemplo, se chamarmos foo com argumentos K = 2 , obteremos uma função que aceita 3 argumentos. Currying, por outro lado, transforma foo em uma cadeia aninhada de funções que aceitam 1 argumento cada. Chamar a versão curry de foo com o primeiro argumento retornará uma função que aceita o segundo argumento e retorna uma função que aceita o terceiro argumento, e assim por diante …
  3. Como conseqüência do acima exposto, currying difere da aplicação parcial no que acontece se você quiser realmente aplicar argumentos um por um. Com funções curried, você obtém essa opção fora da caixa – não há necessidade de usar o utilitário curry em uma determinada função mais de uma vez. Se você quiser aplicar mais um argumento (mas não todos) a uma função parcialmente aplicada, você precisará aplicá-lo parcialmente (usando partial ) novamente.

Mas por que?

Há alguns benefícios de usar o aplicativo parcial e curry no código do aplicativo.

Ambos nos ajudam a criar versões especializadas de funções genéricas, removendo a duplicação e tornando o código mais fácil de ler e compor.

O que significa, no entanto? Vamos ver alguns exemplos mais específicos ilustrando os benefícios.

Separação de preocupações

Uma delas é que, ao usar essas técnicas, você não precisa necessariamente conhecer todos os argumentos de uma determinada função em um só lugar de sua base de código. No exemplo getApiURL acima, um exemplo disso seria "dividir" a funcionalidade em diferentes camadas de código, da seguinte forma:

Este exemplo pode ser um pouco inventado. O ponto que estou tentando fazer, no entanto, é que às vezes é melhor que diferentes “camadas” de seu código sejam responsáveis por contribuir com parâmetros específicos (que melhor se adequam ao seu nível de abstração) para uma parte mais complexa da lógica de negócios.

Legibilidade

Outro benefício do uso de aplicação parcial e currying é que eles podem nos ajudar a criar código mais legível. Compare as seguintes versões da mesma funcionalidade – adicionando 5 a todos os elementos de uma coleção:

Para mim, o uso da versão curry de add é de longe a mais elegante. O que você acha?

Composibilidade

Ainda outro benefício de todo este mumbo-jumbo funcional é a composição de funções mais fácil. Isto é especialmente verdadeiro para currying – funções que aceitam apenas 1 argumento são simplesmente mais fáceis de compor do que outras.

Estaremos voltando à composição de funções em futuras partes desta série, mas, por enquanto, vamos considerar um exemplo simples ilustrando esse aspecto específico.

Compondo funções é sobre passar o resultado de uma função diretamente como um argumento para outra função, da seguinte forma:

Este exemplo é muito fácil de seguir, mas isso é principalmente porque tanto o increment quanto o double são funções unárias (isto é, eles aceitam apenas 1 argumento). Poderíamos usar o mesmo tipo de composição com qualquer função curry – porque eles são todos unários também.

A composição da função não é tão elegante com funções que aceitam mais de um argumento. É por isso que as funções curry são mais fáceis de raciocinar quando compostas com outras funções.

Não se preocupe se você ainda não viu como isso tudo se encaixa. Voltaremos a isso nas próximas partes.

Gotchas!

Ok, então, se a aplicação parcial e (especialmente) o curry são tão impressionantes, por que não os usamos o tempo todo?

Bem, nós apenas poderíamos… 🙂 Em Haskell, por exemplo, o curry automático é embutido no tecido da linguagem em si (a referência de Haskell não é uma surpresa aqui, é?).

Obviamente, há algumas compensações a serem consideradas antes de entrar em uma onda de curry – além de seus colegas o odiarem por escrever um código que eles não entendem.

Eles parecem meio estranhos

Se você não está acostumado a esse estilo de programação, essas técnicas fazem a definição ou o site de chamada da função – ou ambos, um pouco estranho (“por que você precisaria de 5 pares de parênteses aqui?”).

Vamos comparar a mesma peça de funcionalidade escrita usando estilos diferentes:

Indiscutivelmente, a versão tradicional da função parece mais familiar. Esta é definitivamente uma parte da curva de aprendizado, e tudo que posso fazer é assegurar-lhe: fica melhor com o tempo, e depois de algum tempo você pode estar totalmente confortável com a versão curry desta funcionalidade.

Ordenação de argumentos

Isto não é exatamente uma pegadinha ou uma desvantagem, mas definitivamente uma limitação de currying e aplicação parcial. Eles não são de grande ajuda se você quiser chamar uma função fornecendo uma porção de argumentos – mas não os primeiros. Em casos como esse, ainda é melhor criar uma função de wrapper personalizada. Vamos ver um exemplo para deixar isso claro:

Se apiHostname fosse o último parâmetro da função getApiURL original, poderíamos facilmente ter aplicado parcialmente o resourceName e o resourceId e obter uma função que aceitaria o apiHostname como seu parâmetro ("last"). Como nossa intenção não se ajusta à assinatura da função, é mais fácil criar apenas uma função firstAPIUser wrapper.

Argumentos opcionais

A outra coisa que não é muito intuitiva para modelar com aplicação parcial e currying são os parâmetros opcionais. Vamos considerar o seguinte exemplo para currying, utilizando a sintaxe de parâmetros padrão do ES6:

Este não é um disjuntor do negócio, porque, como você pode ver, podemos omitir argumento opcional. Não é tão elegante assim – mesmo que não precisemos passar alguns argumentos, ainda precisamos chamar uma função. Apenas algo para ter em mente.

Mensagens de erro enganosas

Usar o curry às vezes pode fazer com que nossos programas produzam mensagens de erro intrigantes, caso algo dê errado. Por exemplo, vamos considerar o que acontece se nos esquecermos de passar um dos argumentos para uma função.

Como podemos ver, nenhum desses cenários é particularmente melhor. A versão curry explode na nossa cara com um erro não tão descritivo. A versão tradicional, no entanto, continua a funcionar – mas talvez preferíssemos que falhasse.

Novamente, algo para manter em mente – usando versões curry de funções pode torná-los mais "voláteis", mas às vezes pode ser uma coisa boa.

Resumo

Neste artigo, aprendemos o que o currying e a aplicação parcial são – maneiras de aumentar a reutilização do código e melhorar a legibilidade. Também aprendemos quais são as diferenças entre os dois e descobrimos seus prós e contras.

Com isso, adicionamos mais algumas ferramentas à sua caixa de ferramentas funcional. Use-os com sabedoria para melhorar a legibilidade do seu código, mas não se esqueça de suas limitações e desvantagens.

Para um mergulho mais profundo neste assunto, recomendo verificar o seguinte:

Vejo vocês na !