O documento apresenta os conceitos de programação funcional reativa (FRP) através de exemplos de código assíncrono e eventos. A FRP trata valores que mudam ao longo do tempo como valores regulares e utiliza dois conceitos principais: comportamentos e eventos. O framework RxJS é usado para demonstrar como lidar com streams de eventos de forma declarativa ao invés de imperativa.
Programação functional reativa: lidando com código assíncrono
1. Programação Funcional Reativa:
Lidando com código assíncrono
QCon São Paulo, 2014
Leonardo Borges
@leonardo_borges
www.leonardoborges.com
www.thoughtworks.com
2. Sobre
‣ Consultor Senior na ThoughtWorks
Australia
‣ Entusiasta de Programação Functional
‣ Clojure geek
‣ Fundador do Grupo de Usuários Clojure
de Sydney
‣ No momento escrevendo o livro
“Clojure Reactive Programming”
3. var result = 1;!
numbers.forEach(function(n){!
if(n % 2 === 0) {!
result *= n;!
}!
});!
console.log( result );!
// 8!
var numbers = [1,2,3,4,5];
Em programação imperativa, descrevemos computações como uma
serie de ações que modificam o estado do programa
4. Em programação imperativa, descrevemos computações como uma
serie de ações que modificam o estado do programa
var result = 1;!
numbers.forEach(function(n){!
if(n % 2 === 0) {!
result *= n;!
}!
});!
console.log( result );!
// 8!
var numbers = [1,2,3,4,5];
Requer uma variável
para armazenar
estado
5. var result = 1;!
numbers.forEach(function(n){!
if(n % 2 === 0) {!
result *= n;!
}!
});!
console.log( result );!
// 8!
var numbers = [1,2,3,4,5];
Iteramos sobre os
itens do array
Em programação imperativa, descrevemos computações como uma
serie de ações que modificam o estado do programa
6. var result = 1;!
numbers.forEach(function(n){!
if(n % 2 === 0) {!
result *= n;!
}!
});!
console.log( result );!
// 8!
var numbers = [1,2,3,4,5];
E na mesma função
filtramos os itens…
Em programação imperativa, descrevemos computações como uma
serie de ações que modificam o estado do programa
7. var result = 1;!
numbers.forEach(function(n){!
if(n % 2 === 0) {!
result *= n;!
}!
});!
console.log( result );!
// 8!
var numbers = [1,2,3,4,5];
…e efetuamos a
multiplicação.
Em programação imperativa, descrevemos computações como uma
serie de ações que modificam o estado do programa
8. Já em programação funcional, descrevemos o que queremos fazer e
não como queremos fazê-lo
var numbers = [1,2,3,4,5];
var result = numbers!
.filter(function(n){ return n % 2 === 0; })!
.reduce(function(acc, n){!
return acc * n;!
});!
console.log( result );!
// 8!
9. Já em programação funcional, descrevemos o que queremos fazer e
não como queremos fazê-lo
var numbers = [1,2,3,4,5];
var result = numbers!
.filter(function(n){ return n % 2 === 0; })!
.reduce(function(acc, n){!
return acc * n;!
});!
console.log( result );!
// 8!
Nenhuma variável
necessária para a
mesma computação
10. Já em programação funcional, descrevemos o que queremos fazer e
não como queremos fazê-lo
var numbers = [1,2,3,4,5];
var result = numbers!
.filter(function(n){ return n % 2 === 0; })!
.reduce(function(acc, n){!
return acc * n;!
});!
console.log( result );!
// 8! E temos duas funções que
podem ser re-utilizadas de
forma independente
11. A programação funcional reativa traz o mesmo princípio
para valores com os quais lidamos no dia-a-dia:
eventos DOM como clicks, key presses, movimentos do
mouse, chamadas Ajax…
12. Antes de definir um pouco mais
formalmente o que eh FRP, vamos
olhar um exemplo
13. Movimentos em um jogo
var JUMP = 38, CROUCH = 40,!
LEFT = 37, RIGHT = 39,!
FIRE = 32;!
!
function goRight (){!
$('h1').html("Ir para a direita...");!
}!
!
function goLeft (){!
$('h1').html("Ir para a esquerda...");!
}!
!
function jump (){!
$('h1').html("Pular...");!
}!
!
function crouch (){!
$('h1').html("Abaixar...");!
}!
!
function fire (){!
$('h1').html("Atirar...");!
}
14. Movimentos em um jogo:
estilo imperativo
$(window.document).keyup(function(event){!
switch(event.keyCode){!
case JUMP :!
jump();!
break;!
case CROUCH:!
crouch();!
break;!
case LEFT :!
goLeft();!
break;!
case RIGHT :!
goRight();!
break;!
case FIRE :!
fire();!
break;!
};!
});
17. Para a versão funcional, utilizaremos
o framework de FRP RxJS
18. Movimentos em um jogo:
estilo funcional
var source = Rx.Observable.fromEvent(window.document, 'keyup');
function isKey (keyCode){!
return function(event){!
return event.keyCode === keyCode;!
};!
}
source.filter(isKey(FIRE)).subscribe(fire);!
source.filter(isKey(JUMP)).subscribe(jump);!
source.filter(isKey(CROUCH)).subscribe(crouch);!
source.filter(isKey(LEFT)).subscribe(goLeft);!
source.filter(isKey(RIGHT)).subscribe(goRight);!
19. Um pouco mais sobre FRP
‣ Criado em 1997 por Conal Elliott na forma do framework Fran para Haskell
‣ Desde então foi implementada em diversas linguagens e frameworks: Rx(.NET|
JS|Java), reactive-banana (Haskell), Bacon.js, Elm-lang (compile-to-JS) e
outros…
‣ Introduz duas abstrações principais: Behaviors e Events
20. Exemplo de Behavior: posição do cursor do mouse
function mouseXYBehavior(){!
var behavior = new Rx.BehaviorSubject([0,0]);!
$(window.document).mousemove(function(event){!
behavior.onNext([event.clientX, event.clientY]);!
});!
return behavior;!
}
var xyBehavior = mouseXYBehavior();!
xyBehavior.subscribe(function(xy){!
$('h1').html('(' + xy[0] + ',' + xy[1] + ')');!
});!
!
console.log( xyBehavior.value );!
21. Um pouco mais sobre Rx - Rx 101
Rx.Observable.returnValue(42)!
.map(function(value){ return value * 2; })!
.subscribe(function(value){!
console.log( value );!
});!
!
// 84!
22. Um pouco mais sobre Rx - Rx 101
Rx.Observable.fromArray([10, 20, 30])!
.map(function(value){ return value * 2; })!
.reduce(function(acc, value){ return acc + value; })!
.subscribe(function(value){!
console.log( value );!
});!
!
// 120
23. Um pouco mais sobre Rx - Rx 101
function projectRange(n){!
return Rx.Observable.fromArray(_.range(n));!
}!
!
Rx.Observable.fromArray([1, 2, 3])!
.flatMap(projectRange)!
.subscribe(function(value){!
console.log( value );!
});!
!
// 0!
// 0!
// 1!
// 0!
// 1!
// 2
29. E comunicação com a rede?
‣ Callback hell :(
‣ Promises melhoram um pouco mas tem limitações
‣ Funcionam bem para um nível de valores
‣ Porém são um mecanismo pobre de composição
‣ E se tivermos uma série de valores que muda ao decorrer do tempo?
31. O que queremos:
‣ Mostrar os resultados da pergunta atual
‣ Atualizar os resultados a cada 2 segundos
‣ Se a pergunta atual for a mesma que a pergunta anterior, atualizamos o
resultado. Senão:
‣ Paramos com a atualização periódica;
‣ Mostramos uma mensagem de countdown;
‣ Mostramos a pergunta anterior com os resultados;
‣ Reiniciamos a atualização periódica
32. Resultados da parte servidor da aplicação
{!
id: 1,!
question: "Which is the best music style?",!
results: {!
a: 8,!
b: 20,!
c: 15!
}!
}!
39. Recapitulando a idéia principal
function resultsObservable () {!
return Rx.Observable.create(function(observer){!
$.get( "/polls/current/results", function(data) {!
observer.onNext(data);!
observer.onCompleted();!
return function () {!
console.log('disposed');!
};!
});!
});!
}
40. Recapitulando a idéia principal
function resultsConnectable () {!
var obs = Rx.Observable!
.interval(2000)!
.flatMap(resultsObservable)!
.publish()!
.refCount();!
var obs1 = obs.skip(1);!
return Rx.Observable.zipArray(obs, obs1);!
}!
Tornamos os
resultados em um
Observable
41. Recapitulando a idéia principal
function resultsConnectable () {!
var obs = Rx.Observable!
.interval(2000)!
.flatMap(resultsObservable)!
.publish()!
.refCount();!
var obs1 = obs.skip(1);!
return Rx.Observable.zipArray(obs, obs1);!
}!
Duplicamos o
mesmo, pulando um
elemento
42. Recapitulando a idéia principal
function resultsConnectable () {!
var obs = Rx.Observable!
.interval(2000)!
.flatMap(resultsObservable)!
.publish()!
.refCount();!
var obs1 = obs.skip(1);!
return Rx.Observable.zipArray(obs, obs1);!
}!
Finalmente,
“zippamos” os
Observables
43. Show! Será que existe uma forma mais simples de
implementar a mesma funcionalidade?
Certamente!
44. A mesma funcionalidade, explorando mais da API de Rx
function resultsBuffer () {!
return Rx.Observable!
.interval(2000)!
.flatMap(resultsObservable)!
.bufferWithCount(2, 1);!
}
45. Explore e entenda a fundo a API do seu framework de FRP
favorito: muito provavelmente o que você precisa já foi
implementado por alguém
46. Em ambas as soluções, não precisamos de uma
variável extra para armazenar a pergunta anterior
47. Pensar de forma funcional e utilizar um framework
de FRP nos permite implementar soluções simples
e robustas
48. "FRP is about handling time-varying values like they
were regular values" - Haskell Wiki
(FRP lida com valores que mudam ao decorrer do
tempo como se fossem valores regulares)
49. Exemplo bônus: API Reativa para AWS
function resourcesStream (stackName) {!
return Rx.Observable.create(function(observer){!
cloudFormation.describeStackResources({StackName: stackName}, function(err, data){!
if (err) {!
observer.onError("Error");!
observer.onCompleted();!
} else {!
observer.onNext(data);!
observer.onCompleted();!
}!
});!
});!
}
50. Exemplo bônus: API Reativa para AWS
function ec2InstanceStream (physicalResourceIds) {!
return Rx.Observable.create(function(observer){!
ec2.describeInstances({InstanceIds: physicalResourceIds}, function (err, data) {!
if (err) {!
observer.onError("Error");!
observer.onCompleted();!
} else {!
observer.onNext(data);!
observer.onCompleted();!
}!
});!
});!
}
51. Exemplo bônus: API Reativa para AWS
function dbInstanceStream (physicalResourceId) {!
return Rx.Observable.create(function(observer){!
rds.describeDBInstances({DBInstanceIdentifier: physicalResourceId}, function (err, data) {!
if (err) {!
observer.onError("Error");!
observer.onCompleted();!
} else {!
observer.onNext(data);!
observer.onCompleted();!
}!
});!
});!
}
52. Exemplo bônus: API Reativa para AWS
resourcesStream('my-stack')!
.filter(isEC2)!
.map(".PhysicalResourceId")!
.reduce([], function(acc, resource) { acc.push(resource); return acc;})!
.flatMap(ec2InstanceStream);!
53. Exemplo bônus: API Reativa para AWS
var ec2Data = resourcesStream('my-stack')!
.filter(isEC2)!
.map(".PhysicalResourceId")!
.reduce([], function(acc, resource) { acc.push(resource); return acc;})!
.flatMap(this.ec2InstanceStream);
var rdsData = resourcesStream('my-stack')!
.filter(isRDS)!
.map(".PhysicalResourceId")!
.reduce([], function(acc, resource) { acc.push(resource); return acc;})!
.flatMap(dbInstanceStream);!
54. Exemplo bônus: API Reativa para AWS
var ec2Data = resourcesStream('my-stack')!
.filter(isEC2)!
.map(".PhysicalResourceId")!
.reduce([], function(acc, resource) { acc.push(resource); return acc;})!
.flatMap(this.ec2InstanceStream);
var rdsData = resourcesStream('my-stack')!
.filter(isRDS)!
.map(".PhysicalResourceId")!
.reduce([], function(acc, resource) { acc.push(resource); return acc;})!
.flatMap(dbInstanceStream);!
55. Exemplo bônus: API Reativa para AWS
ec2Data!
.merge(rdsData)!
.reduce([], function(acc, resource) { acc.push(resource); return acc;});!