Tableless

Busca Menu

Programação funcional em Javascript. Implementando Curry e Compose, com bind e reduce.

Seja o primeiro a comentar por

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.

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 tras 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 transforma-la em uma cadeia de funções, em que cada uma receba somente um parâmetro.

const curry = (fn, ...args) => {
    if (args.length === fn.length) {
        return fn(...args)
    }
    return curry.bind(this, fn, ...args)
}

Vamos agora ver o teste dessa função:

describe('curry', () => {
  it('returns the curried function', () => {
      const add = (a, b) => a + b

      expect(FunctionUtils.curry(add, 1, 2)).toBe(3)
      expect(FunctionUtils.curry(add)(1)(2)).toBe(3)
      expect(FunctionUtils.curry(add)(1, 2)).toBe(3)
      expect(FunctionUtils.curry(add, 1)(2)).toBe(3)
  })
})

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:

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

devtools e já testar).

"use strict";

function myNumbers(x, y, z){
  console.log(x);
  console.log(y);
  console.log(z);
}

var foo = myNumbers.bind(this, 1);
foo(); 
// 1
// undefined
// undefined

var bar = foo.bind(this, 2);
bar();
// 1
// 2
// undefined

var baz = bar.bind(this, 3);
baz();
//1
//2
//3

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.

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:

const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)))

Vamos ao teste dessa função:

describe('compose', () => {
    it('composes functions', () => {
        const toUpperCase = x => x.toUpperCase()
        const exclaim = x => `${x}!`
        const moreExclaim = x => `${x}!!`

        expect(FunctionUtils.compose(toUpperCase, exclaim)('test')).toBe('TEST!')
        expect(FunctionUtils.compose(toUpperCase, exclaim, moreExclaim)('test')).toBe('TEST!!!')
    })
})

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:

compose-print

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

<

p 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:

Publicado no dia