Programação funcional – parte 2
Recursão, Currying
Na primeira parte da série sobre Programação Funcional falamos de Transparência Referencial, Pureza e Imutabilidade. Continuando, iremos agora falar de dois outros conceitos básicos, porém bastante úteis na Programação Funcional que são a Recursão e o Currying.
Recursão
Este conceito, também largamente utilizado na programação procedural, abre caminho para potencializar o uso de uma função. Com uma função que chama ela mesma em certas circunstâncias, criamos algoritmos capazes de percorrer estruturas aninhadas de arrays e objetos a fim de calcular valores, processar dados ou adaptar estas estruturas gerando outras diferentes.
No exemplo abaixo temos uma expressão que percorre um array multidimensional e calcula a soma de todos os números contidos independente do nível em que eles se encontram:
const recursiveSum = collection => collection.reduce((prev, curr) => (Array.isArray(curr) ? prev + recursiveSum(curr) : prev + curr), 0) const collection = [1, [2, [3, 4]], 5] recursiveSum(collection) // 15
A Recursão pode também ser usada com outras estruturas como números e strings. No exemplo abaixo, criamos uma expressão funcional para gerar o enésimo número na sequência de Fibonacci:
const fibonacci = n => (n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2)) fibonacci(10) // 55
Aviso: isto está longe de ser a melhor implementação do enésimo número Fibonacci. Porém serve bem ao propósito de ilustração de uma recursão. Existem várias implementações mais performáticas que usam uma matemática mais sofisticada.
Currying
Outro conceito bastante usado na Programação Funcional e que assusta alguns pela peculiaridade de sua sintaxe é o Currying.
Currying é usado quando quebramos uma função que recebe vários argumentos em várias funções que recebem apenas um argumento cada.
Podemos ainda tentar explicar o Currying como uma factory the funções onde cada parâmetro gera um contexto, e portanto, também um ponto para reutilização.
const multiply = factorOne => factorTwo => factorOne * factorTwo multiply(2)(5) // 10
Veja no exemplo abaixo uma forma de reutilização de uma função implementada com Currying:
const tenTimes = multiply(10) tenTimes(100) // 1000
Esta forma de reaproveitamento parcial de funções implementadas usando Currying chamamos de Aplicação Parcial.
Currying também é útil quando precisamos rodar partes da função em tempos diferentes, por exemplo, podemos querer rodar o “primeiro nível” de uma função quando a aplicação inicializa, guardar as funções resultantes destas chamadas e utilizá-las em runtime quantas vezes forem necessárias. Isso é bastante útil para implementação de um sistema de middlewares, como no exemplo abaixo:
const startMiddlewares = (...middlewares) => { const [first, ...others] = middlewares.map(middleware => middleware()) return payload => others.reduce((previous, current) => current(previous), first(payload)) } const fooMiddleware = () => payload => Object.assign({}, payload, { foo: 'bar' }) const bazMiddleware = () => payload => Object.assign({}, payload, { baz: 'qux' }) const runMiddlewares = startMiddlewares(fooMiddleware, bazMiddleware) const initialPayload = {} runMiddlewares(initialPayload) // { foo: 'bar', baz: 'qux' }
Em linguagens de programação puramente funcionais, que tem suporte ao currying nativo, o currying é quase sempre a melhor (ou às vezes a única) forma de se passar vários argumentos para uma função.
Por hoje é só pessoal. Se tiverem algo a acrescentar, corrigir ou alguma dúvida, deixem seus comentários abaixo.
Até o próximo encontro para podermos falar mais sobre Programação Funcional.