Programação funcional em Javascript. Desconstruindo o Pareto.js

Como podemos implementar Curry e Compose, com bind e reduce.

Nos últimos tempos só se fala em programação funcional, seus benefícios, funções puras, dados imutáveis, composição de funções, etc. Caso precise de saber mais, recomendo esse post do Matheus Lima:

Atualmente temos diversas libs que auxiliam o javascript na missão de ser funcional, Lodash, Underscore e Ramda são uma delas. Então porque estarei falando do Pareto.js? Simples como o Princípio de Pareto, a lib criada tem o objetivo de ser leve e resolver 80% dos seus problemas com 20% de código.

Geralmente procuro aprender algo desmitificando a “mágica” por trás da implementação. Foi assim quando comecei a aprender Angular, e agora o mesmo está sendo aplicado à programação funcional. Por isso nesse post vamos avaliar as implementações de Curry e Compose do Pareto.js.

Curry

Curry é a ação de pegar uma função que receba múltiplos argumentos e transformá-la em uma cadeia de funções, em que cada uma receba somente um parâmetro.

Vamos agora ver o teste dessa função:

Para começarmos a desmitificar a mágica, temos duas perguntas a serem feitas:

  • Como a nossa função curry irá armazenar os parâmetros já passados?
  • O que o Function.prototype.bind() tem a ver com isso?

Function.prototype.bind()

Comumente usamos .bind() para passarmos para uma função um contexto para sua execução, porém nos esquecemos de algo importante, como dito na documentação do developer.mozilla.org:

Partial Functions
The next simplest use of bind() is to make a function with pre-specified initial arguments. These arguments (if any) follow the provided this value and are then inserted at the start of the arguments passed to the target function…

Resumindo:

Um dos usos de bind() é construir uma função com argumentos iniciais pré-especificados. Esses argumentos, serão passados após o valor de This e serão inseridos no inicio dos argumentos passados para a função de destino

Difícil de entender? Então vamos a mais um exemplo (em ES5 para que você possa abrir o devtools e já testar).

Reparem que a função myNumbers espera três parâmetros, a cada vez que chamamos .bind(this, val), a função retornada pelo método .bind() automagicamente guarda o argumento passado.

E com isso chegamos à implementação do curry no pareto.js, que irá chamar curry.bind(this, fn, ...args), empilhando os parâmetros no spread operator ...args até que a quantidade de argumentos seja a mesma que a função espera (args.length === fn.length). Caso não tenha entendido o que é …args, dê uma lida em spread operator.

Compose

Como o próprio nome sugere, Compose é construir funções mais complexas através de funções mais simples, compondo-as. Vamos à implementação no Pareto.js:

Vamos ao teste dessa função:

E assim temos uma pergunta:

  • O que Array.prototype.reduce() está fazendo aí no meio ?

Array.prototype.reduce()

Em geral pensamos no .reduce() como um acumulador, porém somente no sentido de soma de valores e não de composição. Sabemos que o .reduce() aplica uma função de callback sobre um acumulador, varrendo todos os elementos do array. Vamos começar a desconstrução do nosso compose:

  • Sabemos que ele recebe um array de funções como argumentos, através do spread operator …args;
  • A função de callback do .reduce(), que será executada sobre cada item do nosso array, pode receber até 4 parâmetros, sendo eles: previousValue, currentValue, index, array. Porém aqui só iremos utilizar os dois primeiros (previousValue e currentValue). Lembrando que na primeira chamada à nossa função de callback, previousValue será o valor do primeiro elemento do array e currentValue será o valor do elemento seguinte;
  • A nossa função de callback irá compor a função passada em previousValue com a que está em currentValue, adicionando na declaração da função que ela poderá receber N argumentos (…args). Resultando em previousValue(currentValue(…args)).

De acordo com o nosso testes, vamos observar os passos de execução em uma tabela:

Chamadas da função de callback do .reduce()

E com isso temos o resultado da função mais interna (moreExclaim) alimentando as funções mais externas (exclaim e depois toUpperCase).

E é isso pessoal. Espero que tenha ajudado à vocês a entenderem a relação de curry e compose com .bind() e .reduce(). Feedbacks são mais do que bem-vindos e incentivados. Até a proxima.

Fontes:
https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
https:[email protected][email protected]vez-c676489be08b#
https://github.com/concretesolutions/pareto.js
https://pt.wikipedia.org/wiki/Princ%C3%ADpio_de_Pareto