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

junho 29, 2016 1:46 pm Publicado por Deixe um comentário

fjs

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:

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

<p Difícil de entender? Então vamos a mais um exemplo (em ES5 para que você possa abrir o 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.

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

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:


Este artigo foi escrito por Bruno Gonçalves.

Visite o nosso site para mais posts sobre desenvolvimento web! Tableless.

Source: Tableless

Categorizados em:

Este artigo foi escrito pormajor

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *