Currying e Aplicação Parcial em JavaScript
Currying e aplicação parcial são técnicas poderosas de programação funcional que melhoram a modularidade e a reutilização do seu código JavaScript.
Currying e aplicação parcial são técnicas poderosas de programação funcional que podem melhorar significativamente a modularidade e a reutilização do seu código JavaScript. Ambas dependem de closures — a capacidade de uma função interna de lembrar as variáveis da função externa que a criou — de modo que uma função retornada mantém os argumentos que você já forneceu.
Este artigo aborda o que é currying e por que ele é importante, como escrever um helper curry reutilizável, como a aplicação parcial difere do currying, casos de uso reais e as boas práticas (e armadilhas) a ter em mente.
Entendendo o Currying
Currying é uma técnica em que uma função é transformada em uma sequência de funções, cada uma recebendo um único argumento. Ele permite que você decomponha uma função que recebe vários argumentos em uma série de funções unárias (de argumento único).
Exemplo de Currying
Considere uma função simples que soma três números:
function add(a, b, c) {
return a + b + c;
}Podemos transformar essa função em uma versão com currying:
Explicação:
- A função
addrecebe três argumentos e retorna a soma deles. - A função
curryAddé uma versão com currying deadd. Ela recebe um argumentoae retorna outra função que recebeb. Essa segunda função retorna mais uma função que recebec. A função mais interna retorna a soma dea,bec.
Benefícios do Currying
- Reutilização: Funções com currying permitem criar novas funções fixando alguns argumentos. Isso melhora a reutilização ao permitir criar facilmente versões especializadas de uma função sem duplicar código.
Aqui, curriedAdd é uma versão com currying da função add. Ela nos permite criar funções especializadas como add5 fixando o primeiro argumento (a) em 5. Isso promove a reutilização de código, pois podemos criar várias funções especializadas sem repetir a lógica de adição.
- Composição de Funções: O currying facilita a composição de funções. Composição de funções é o processo de combinar duas ou mais funções para produzir uma nova função.
Neste exemplo, composemos as funções multiply e addOne usando currying. A forma concisa a => b => a * b depende de arrow functions, que tornam funções aninhadas de argumento único fáceis de ler. Ao aplicar currying nessas funções, podemos criar facilmente uma nova função addOneThenMultiplyBy5, que primeiro adiciona 1 à entrada (x) e depois multiplica o resultado por 5. Isso demonstra como o currying facilita a composição de funções, tornando mais simples criar novas funções combinando as existentes.
Implementando Currying em JavaScript
Podemos criar uma função utilitária para aplicar currying a qualquer função. Aqui está uma implementação de uma função de currying genérica:
Explicação:
-
Função de Currying (
curry):- A função
curryrecebe outra funçãofncomo entrada. - Ela retorna uma nova função chamada
curried. - Essa função
curriedaceita qualquer número de argumentos usando a sintaxe de parâmetro rest (...args).
- A função
-
Função com Currying (
curried):- Dentro de
curried, verifica-se se o número de argumentos fornecidos (args.length) é maior ou igual ao número de argumentos esperados pela função originalfn(fn.length). - Se forem fornecidos argumentos suficientes, a função original
fné chamada com esses argumentos usandofn.apply(this, args). - Se não forem fornecidos argumentos suficientes, é retornada uma nova função que aceita mais argumentos (
nextArgs) usando o operador spread (...nextArgs). - Essa nova função chama
curriedrecursivamente com os argumentos combinados (args.concat(nextArgs)), garantindo que todos os argumentos sejam eventualmente coletados antes de chamar a função originalfn.
Nota:
fn.lengthconta apenas parâmetros sem valores padrão e ignora parâmetros rest. - Dentro de
-
Exemplo de Uso:
- Definimos uma função
multiplyque recebe três argumentos e retorna o produto deles. - Criamos uma versão com currying da função
multiplypassando-a para a funçãocurry, que retorna uma nova funçãocurriedMultiply. - Agora,
curriedMultiplypode ser chamada com um, dois ou três argumentos. - Cada vez que chamamos
curriedMultiplycom um ou mais argumentos, ela retorna uma nova função até que todos os argumentos sejam coletados, momento em que retorna o resultado da multiplicação dos argumentos.
- Definimos uma função
Explorando a Aplicação Parcial
Aplicação parcial é uma técnica em que você cria uma nova função pré-preenchendo alguns argumentos da função original. Isso é especialmente útil para criar funções especializadas.
Exemplo de Aplicação Parcial
Considere a seguinte função que formata uma mensagem:
function formatMessage(greeting, name) {
return `${greeting}, ${name}!`;
}Podemos criar uma função com aplicação parcial:
Explicação:
- A função
formatMessagerecebe dois argumentos,greetingename, e retorna uma mensagem formatada. - A função
partialrecebe uma funçãofne alguns argumentos predefinidos (...presetArgs). Ela retorna uma nova função que recebe os argumentos restantes (...laterArgs). - Quando a nova função é chamada, ela combina
presetArgselaterArgse chama a função originalfncom esses argumentos. - Usando
partial, criamosgreetHello, uma função que sempre usa "Hello" como saudação. Quando chamada com um nome, retorna a mensagem completa.
Benefícios da Aplicação Parcial
-
Simplificação: Crie funções mais simples a partir de funções mais complexas
Suponha que temos uma função que calcula o preço final de um item após aplicar desconto e imposto.
function calculateFinalPrice(price, discount, tax) {
return price - (price * discount) + (price * tax);
}Essa função requer três argumentos, tornando-a um pouco inconveniente de usar repetidamente quando o desconto e as alíquotas de imposto são frequentemente os mesmos. Com aplicação parcial, podemos simplificar isso.
No exemplo acima, applyDiscountAndTax é uma função com aplicação parcial que predefiniu os valores de discount e tax. Isso facilita o cálculo do preço final para diferentes itens sem especificar repetidamente as alíquotas de desconto e imposto.
-
Reutilização de Código: Reutilize a lógica comum de funções com diferentes argumentos predefinidos
Imagine que temos uma função que registra mensagens com diferentes níveis de severidade.
function logMessage(level, message) {
console.log(`[${level}] ${message}`);
}Podemos criar funções reutilizáveis para diferentes níveis de log usando aplicação parcial.
Aqui, createLogger é uma função com aplicação parcial que define o argumento level. As funções infoLogger e errorLogger podem agora ser usadas para registrar mensagens com os níveis de log predefinidos, reutilizando a lógica comum de logMessage.
- Legibilidade Melhorada: Torna o código mais legível ao decompor funções complexas Considere uma função que formata datas em diferentes estilos.
function formatDate(date, format) {
const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
if (format === 'US') {
options.month = 'long';
} else if (format === 'EU') {
options.day = 'numeric';
options.month = 'numeric';
}
return new Date(date).toLocaleDateString(undefined, options);
}Usando aplicação parcial, podemos criar funções mais legíveis para diferentes formatos de data.
createDateFormatter aplica parcialmente o argumento format, resultando em funções específicas para formatos de data dos EUA e da Europa. Essa decomposição torna o código mais legível e fácil de entender, pois cada função de formatação é dedicada a um formato específico.
Esses exemplos ilustram como a aplicação parcial em JavaScript pode simplificar funções complexas, melhorar a reutilização de código e aumentar a legibilidade, tornando o código mais fácil de gerenciar e entender.
Currying vs. Aplicação Parcial
Essas duas técnicas são intimamente relacionadas e frequentemente confundidas, mas não são a mesma coisa:
- Currying transforma uma função de N argumentos em N funções aninhadas que cada uma recebe exatamente um argumento. Uma função totalmente com currying é sempre chamada um argumento por vez:
f(a)(b)(c). - Aplicação parcial fixa alguns argumentos de uma função e retorna uma nova função que recebe o restante em uma única chamada:
g = partial(f, a)e depoisg(b, c).
Em resumo: currying é sobre a forma da função (uma cadeia de chamadas unárias), enquanto a aplicação parcial é sobre pré-preencher argumentos. Observe que o helper curry genérico acima é mais flexível do que o currying estrito — ele aceita argumentos em grupos (curriedMultiply(2, 3)(4)), portanto borra a linha e efetivamente também suporta aplicação parcial.
Boas Práticas para Usar Currying e Aplicação Parcial
Mantenha as Funções Puras
- Garanta que as funções com currying e aplicação parcial permaneçam puras, sem efeitos colaterais. Isso as torna mais fáceis de raciocinar e testar.
Use Quando Apropriado
- Use currying e aplicação parcial quando eles se encaixam naturalmente no problema em questão.
- Evite usar excessivamente essas técnicas, pois elas podem tornar o código mais difícil de entender se não forem usadas com critério.
Aproveite a Composição de Funções
- Combine funções com currying para criar funcionalidades mais complexas. O currying funciona bem com composição de funções, levando a um código mais modular.
Documentação e Nomenclatura
- Documente adequadamente as funções com currying e aplicação parcial para indicar seu uso esperado.
- Use nomes claros e descritivos para as funções a fim de transmitir seu propósito.
Fique Atento a Estas Armadilhas
fn.lengthé não confiável para currying automático. Um helpercurrygenérico que depende defn.lengthse comportará incorretamente com parâmetros padrão ou parâmetros rest, pois esses não são contados. Se uma função os usa, passe a aridade esperada explicitamente em vez de confiar emfn.length.- Excesso de currying prejudica a legibilidade. Uma cadeia longa
f(a)(b)(c)(d)é mais difícil de ler do que uma única chamada bem nomeada. Recorra ao currying quando ele genuinamente eliminar repetição. - Cuidado com
this. Arrow functions não têm seu própriothis, portanto uma cadeia com currying escrita com arrows não pode ser usada como um método que depende do objeto chamador. Se você precisar dothisdinâmico, consulte vinculação de funções.
Se você usa currying e aplicação parcial com frequência, considere usar a biblioteca Lodash, que fornece funções utilitárias convenientes como _.curry e _.partial.
Combinando com Métodos de Array
O currying pode ser combinado efetivamente com métodos de array como map, filter e reduce para obter código conciso e expressivo.
Explicação:
- A função
curriedMultiplyé usada para criarmultiplyByTwo, uma função que multiplica seu argumento por 2. - O array
numbersé transformado usandomap, aplicandomultiplyByTwoa cada elemento, resultando em um novo array de números dobrados.
Conclusão
Currying e Aplicação Parcial em JavaScript oferecem técnicas poderosas para simplificar a composição de funções, melhorar a reutilização de código e aumentar a legibilidade. O currying transforma funções com múltiplos argumentos em uma série de funções unárias, permitindo um código mais flexível e modular. A aplicação parcial permite predefinir argumentos de funções, facilitando a reutilização de código e a simplificação de funções complexas. Ao aproveitar esses conceitos de programação funcional, os desenvolvedores podem escrever código JavaScript mais limpo, conciso e de fácil manutenção.