Programação Funcional e Reativa com JavaScript

William Grasel

@willgmbr

Programação Funcional

  • Programação declarativa
  • Isolando efeitos colaterais
  • Abstrações Matemáticas

Linguagens "Puramente" Funcionais

  • Visam Pureza Matemática
  • Isolam efeitos colaterais ao máximo
  • Imutáveis por padrão
  • Tem funções como tipo primitivo
  • Comportamento como parâmetro

Programação Funcional não é algo novo!

  • Foi inventado antes mesmo da programação imperativa
  • Mas era inviável para a época
  • Computacionalmente muito custoso
  • Tanto de memória quanto de processamento
  • Não tinha boas abstrações para efeitos colaterais
  • Programação imperativa deslanchou na frente

Todos esse problemas foram resolvidos com o tempo!

Venha para o lado negro da força!

Vamos revisar alguns conceitos essenciais!

Funções "Puras"

  • Comportamento consistente
  • Pode ser trocada pelo seu resultado final
  • Não mantém estado próprio entre chamadas
  • Não foge do seu escopo
  • Não tem efeitos colaterais

Comportamento Consistente


  const double = x => x * 2;

  double(2); // 4

  double(2); // 4

  double(2); // 4

  const double = x => x * 2;

  let r1 = double(2) + double(4);

  let r2 = 4 + 8;

  r1 === r2; // true

Efeitos Colaterais


  function funcaoNadaPura(obj) {
    console.log('io');
    return 'concatena'
      + (++obj.num)
      + (++window.qqcoisa);
  }

  let meuObj = { num: 1 },
      r1 = funcaoNadaPura(meuObj),
      r2 = funcaoNadaPura(meuObj);

  r1 != r2; //false

Por que utilizar

Funções Puras?

  • Mais fácil de testar
  • Mais fácil de compor
  • Diminui complexidade
  • Menor taxa de bugs
  • Principalmente em programas concorrentes e assíncronos

Funções de Alta Classe

  • Padrão de composição de funções
  • Recebe uma ou mais funções como argumento
  • E / ou retorna uma outra função

Exemplos de Alta Classe


  function exibeResultado() {
    console.log(arguments);
  }

  $('button').click(exibeResultado);

  $http.get('//url')
    .then(exibeResultado);

  const a = [1, 2, 3];

  const b = a.map(i => i * 10)
   // [10, 20, 30]
   .filter(i => i > 10)
   // [20, 30]
   .some(i => i > 25);
   // true

f(x) = r

f(x, y, z)

 = f(x)

(y)

(z)

Currying

Técnica de passar argumentos para uma função de forma parcial, sob demanda, retornando uma outra função até que todos os argumentos sejam dados.

Exemplos de Currying


  function makeArray(a, b, c, d) {
    return [a, b, c, d];
  }

  // Javascript Puro
  makeArray
   .bind(null, 1)
   .bind(null, 2, 3)
   (4) // [1, 2, 3, 4]

  function makeArray(a, b, c, d) {
    return [a, b, c, d];
  }

  // Com Lodash ou Underscore.js
  const curryMA = _.curry(makeArry);

  curryMA(1)(2)(3)(4);
  curryMA(1, 2)(3, 4);
  curryMA(1, 2, 3)(4);
  // [1, 2, 3, 4]

  function changeTab(newTab, ev){...}
  
  const curredChangeTab = _.curry(changeTab);

  $('.opt1').click(curredChangeTab(1));
  $('.opt2').click(curredChangeTab(2));
  $('.opt3').click(curredChangeTab(3));
  $('.opt4').click(curredChangeTab(4));
  $('.opt5').click(curredChangeTab(5));

PERIGO!!!

Zona de grande simplificação de temas complexos e quase religiosos para amantes de programação funcional, os mais preciosistas podem se ofender.

Functors

  • É um padrão de transformação de objetos através de funções
  • São objetos que tem uma ou mais funções de alta classe
  • Que recebe uma função como parâmetro
  • Para gerar uma nova versão de si mesmo
  • Que seja consistente com a versão anterior
  • Permitindo um encadeamento indefinido de transformações

Exemplos conhecidos de Functors


  [1, 2, 3]
  .map(i => i + 10)
  .map(i => i + 'p')

  // ['10p', '20p', '30p']

  Promise.resolve(1)
  .then(i => i + 10)
  .then(i => i + 'p')

  // Promise {
  //   [[PromiseStatus]]: "resolved",
  //   [[PromiseValue]]: '10p'
  // }

Falta de consistência


  [1, 2, 3]
  .comcat([10, 20, 30])
  // [1, 2, 3, 10, 20, 30]
  .filter(i => i > 10)
  // [20, 30]
  .some(i => i > 25);
  // true

  // Exemplos de funções de
  // alta classe normalmente
  // confundidos com Functors.

Produzindo seu próprio Functor


  class OlaMundo {
    constructor(val) {
      this.val = val;
    }
    meuMap(func) {
      return new OlaMundo(
        func(this.val)
      );
    }
  }

  new OlaMundo(1)
  .meuMap(v => v * 10)
  .meuMap(v => v + 'p')

  // OlaMundo {
  //   val: '10p'
  // }

Transformando e Achatando Objetos


  const array = [1, 2, 3];

  const array = [1, 2, 3];

  const arrayArray = array
  .map( i => [ i*i+1, i*i+2 ] );
  // [[2, 3], [4, 5], [6, 7]]

  const array = [1, 2, 3];

  const arrayArray = array
  .map( i => [ i*i+1, i*i+2 ] );
  // [[2, 3], [4, 5], [6, 7]]

  // JavaScript puro:
  arrayArray.reduce((i, newArray) => {
    return newArray.concat(i);
  }, []);
  // [2, 3, 4, 5, 6, 7]

  const array = [1, 2, 3];

  const arrayArray = array
  .map( i => [ i*i+1, i*i+2 ] );
  // [[2, 3], [4, 5], [6, 7]]

  // JavaScript puro:
  arrayArray.reduce((i, newArray) => {
    return newArray.concat(i);
  }, []);
  // [2, 3, 4, 5, 6, 7]

  // Com Lodash ou UnderscoreJS:
  _.flatten(arrayArray);

  const array = [1, 2, 3];

  const arrayArray = array
  .map( i => [ i*i+1, i*i+2 ] );
  // [[2, 3], [4, 5], [6, 7]]

  // JavaScript puro:
  arrayArray.reduce((i, newArray) => {
    return newArray.concat(i);
  }, []);
  // [2, 3, 4, 5, 6, 7]

  // Com Lodash ou UnderscoreJS:
  _.flatten(arrayArray);

  // Melhor ainda, em uma única linha:
  _.flatMap(array, i => [ i*i+1, i*i+2 ]);

Monads

  • Mais um padrão de transformação de objetos
  • São uma especialização de Functors
  • Que ao invés de retornar um tipo simples em cada transformação
  • Retorna um objeto do mesmo tipo para cada item transformado
  • Por fim junta todos em um só objeto de um mesmo nível
  • Permitindo um encadeamento indefinido dessas transformações

Encadeando nosso FlatMap


  _.flatMap([], i => [i]);

  _.flatMap([], i => [i]);

  _.chain([1, 2])

  _.flatMap([], i => [i]);

  _.chain([1, 2])
  .flatMap( i => [ i*i+1, i*i+2 ] )

  _.flatMap([], i => [i]);

  _.chain([1, 2])
  .flatMap( i => [ i*i+1, i*i+2 ] )
  .flatMap( i => [ i+1, i*i ] )

  _.flatMap([], i => [i]);

  _.chain([1, 2])
  .flatMap( i => [ i*i+1, i*i+2 ] )
  .flatMap( i => [ i+1, i*i ] )
  .value()
  // [3, 4, 4, 9, 6, 25, 7, 36]

Exemplo conhecido de Monads


  Promise.resolve(1)

  Promise.resolve(1)
  
  .then( data => 1 * 10 )

  Promise.resolve(1)
  
  .then( data => 1 * 10 )
  
  .then( data => $http.get(data) )

  Promise.resolve(1)
  
  .then( data => 1 * 10 )
  
  .then( data => $http.get(data) )
  
  .then( data => {
    console.log(JSON.stringify(data));
  });

Validando Monads


  // Identidade a esquerda
  Promise.resolve(x).then(fn) == Promise.resolve(fn(x));

  // Identidade a direita
  Promise.resolve(x).then(x => x) == Promise.resolve(x);

  // Associatividade
  Promise.resolve(x).then(fn).then(gn)
  ==
  Promise.resolve(x).then(x => {
    Promise.resolve(fn(x)).then(Promise.resolve(gn(x)))
  });

Programação Reativa

  • Paradigma de programação baseado em fluxos assíncronos de dados, também conhecido como Streams
  • Mantendo um fluxo unidirecional de interação entre os dados
  • De forma declarativa, fácil de ler, entender e manter
  • Facilitando a implementação de estados imutáveis

Mas o que são Streams?

  • Implementação do PubSub pattern, também conhecidos como Observers, ou simplesmente callback de eventos
  • Fluxos de dados assíncronos que transitam pela aplicação
  • Diferentes Streams podem ser mergeados e transformados
  • Através de paradigmas funcionais como Functor e Monads

Exemplo de Streams


  const up = $('#up').asEventStream('click').map(1);

  const up = $('#up').asEventStream('click').map(1);
  
  const down = $('#down').asEventStream('click').map(-1);

  const up = $('#up').asEventStream('click').map(1);
  
  const down = $('#down').asEventStream('click').map(-1);
  ​
  const count = up.merge(down)

  const up = $('#up').asEventStream('click').map(1);
  
  const down = $('#down').asEventStream('click').map(-1);
  ​
  const count = up.merge(down)
    .scan(0, (x, y) => x + y );

  const up = $('#up').asEventStream('click').map(1);
  
  const down = $('#down').asEventStream('click').map(-1);
  ​
  const count = up.merge(down)
    .scan(0, (x, y) => x + y );
  ​
  count.assign($('#counter'), 'text');

Sera que vale a pena?

Por onde eu começo?

Referencias e Links

Perguntas??

Obrigado!

Programação Funcional e Reativa com JavaScript

By William Grasel

Programação Funcional e Reativa com JavaScript

Graças a presença de funções como tipo primitivo, Javascript é uma linguagem que nasceu com suporte necessário para programação funcional desde o principio, vamos ver como tirar melhor proveito disso e entender o que é programação reativa e funcional na prática, com exemplos e ferramentas para utilizar streams, imutabilidade e um fluxo único de dados na sua aplicação.

  • 2,993