Programação Funcional e Reativa com JavaScript

William Grasel

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

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

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;
    }
    bind(func) {
      return new OlaMundo(
        func(this.val)
      );
    }
  }

  new OlaMundo(1)
  .bind(v => v * 10)
  .bind(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], [5, 6], [10, 11]]

  const array = [1, 2, 3];

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

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

  const array = [1, 2, 3];

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

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

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

  const array = [1, 2, 3];

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

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

  // 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!

@willgmbr

fb.com/wgrasel