SlideShare une entreprise Scribd logo
1  sur  72
Télécharger pour lire hors ligne
Taming Asynchronous Workflows with
Functional Reactive Programming
EuroClojure - Kraków, 2014
Leonardo Borges
@leonardo_borges
www.leonardoborges.com
www.thoughtworks.com
About
‣ ThoughtWorker
‣ Functional Programming & Clojure
advocate
‣ Founder of the Sydney Clojure User
Group
‣ Currently writing “Clojure Reactive
Programming”
Taming Asynchronous Workflows with
Functional Reactive Programming
Taming Asynchronous Workflows with
Functional Reactive Programming
Compositional Event Systems
http://bit.ly/conal-ces
http://bit.ly/rx-commit
http://bit.ly/reactive-cocoa-commit
There are only two hard things in
Computer Science: cache invalidation
and naming things. - Phil Karlton
:)
Ok, so what’s the difference?
‣ Created in 1997 by Conal Elliott for the reactive animations framework Fran, in Haskell
‣ Since then other implementations have appeared: reactive-banana, NetWire, Sodium
(all in Haskell)
‣ And then FRP-inspired ones: Rx[.NET | Java | JS], Baconjs, reagi (Clojurescript)
‣ Main abstractions: Behaviors e Events
More about FRP
‣ Created in 1997 by Conal Elliott for the reactive animations framework Fran, in Haskell
‣ Since then other implementations have appeared: reactive-banana, NetWire, Sodium
(all in Haskell)
‣ And then FRP-inspired ones: Rx[.NET | Java | JS], Baconjs, reagi (Clojure[script])
‣ Main abstractions: Behaviors e Events
‣ Traditionally defined as:
type Behavior a = [Time] -> [a]!
type Event a = [Time] -> [Maybe a]
More about FRP
We’ll be focusing on
Compositional Event Systems
Prelude
Imperative programming describes computations as a series of actions
which modify program state
var result = 1;!
numbers.forEach(function(n){!
if(n % 2 === 0) {!
result *= n;!
}!
});!
console.log( result );!
// 8!
var numbers = [1,2,3,4,5]; Requires a variable
to store state
var result = 1;!
numbers.forEach(function(n){!
if(n % 2 === 0) {!
result *= n;!
}!
});!
console.log( result );!
// 8!
var numbers = [1,2,3,4,5];
We iterate over the
array
Imperative programming describes computations as a series of actions
which modify program state
var result = 1;!
numbers.forEach(function(n){!
if(n % 2 === 0) {!
result *= n;!
}!
});!
console.log( result );!
// 8!
var numbers = [1,2,3,4,5];
And then we filter the
items…
Imperative programming describes computations as a series of actions
which modify program state
var result = 1;!
numbers.forEach(function(n){!
if(n % 2 === 0) {!
result *= n;!
}!
});!
console.log( result );!
// 8!
var numbers = [1,2,3,4,5];
…and perform the
multiplication in the
same function
Imperative programming describes computations as a series of actions
which modify program state
(def numbers [1 2 3 4 5])!
!
(def result!
(->> numbers!
(filter even?)!
(reduce *)))!
!
(prn result)!
!
;; 8
In functional programming, we describe what we want to do but not
how we want it done
That is, there are no variables with
local state and we get better re-use
from single purpose functions
Compositional Event Systems brings the same
principle to values we work with daily: DOM
events (clicks, key presses, mouse movement),
Ajax calls…
Let’s look at an example
Game movements in
Javascript
var JUMP = 38, CROUCH = 40,!
LEFT = 37, RIGHT = 39,!
FIRE = 32;!
!
function goRight (){!
$(‘h1').html("Going right...");!
}!
!
function goLeft (){!
$(‘h1').html("Going left...");!
}!
!
function jump (){!
$('h1').html("Jumping...");!
}!
!
function crouch (){!
$('h1').html("Crouching...");!
}!
!
function fire (){!
$('h1').html("Firing...");!
}
Game movements in
Javascript
$(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;!
};!
});
We now have limitations similar to
the multiplication example
Let us think key presses as a list of keys
over a period of time
This leads to the following solution
Reactive game movements
(def UP 38) (def RIGHT 39)!
(def DOWN 40) (def LEFT 37)!
(def FIRE 32) (def PAUSE 80)!
!
!
(def source (.fromEvent js/Rx.Observable js/window "keyup"));!
!
(-> source (.filter #(= UP %)) (.subscribe jump))!
(-> source (.filter #(= DOWN %)) (.subscribe crouch))!
(-> source (.filter #(= RIGHT %)) (.subscribe go-right))!
(-> source (.filter #(= LEFT %)) (.subscribe go-left))!
(-> source (.filter #(= FIRE %)) (.subscribe fire))!
http://bit.ly/rxjava-github
http://bit.ly/rxjs-github
Reactive game movements
(def UP 38) (def RIGHT 39)!
(def DOWN 40) (def LEFT 37)!
(def FIRE 32) (def PAUSE 80)!
!
!
(def source (.fromEvent js/Rx.Observable js/window "keyup"));!
!
(-> source (.filter #(= UP %)) (.subscribe jump))!
(-> source (.filter #(= DOWN %)) (.subscribe crouch))!
(-> source (.filter #(= RIGHT %)) (.subscribe go-right))!
(-> source (.filter #(= LEFT %)) (.subscribe go-left))!
(-> source (.filter #(= FIRE %)) (.subscribe fire))!
http://bit.ly/rxjava-github
http://bit.ly/rxjs-github
Behaviours
;; behavior examples!
(def time-b (r/behavior (System/currentTimeMillis)))!
@time-b!
;; 1403691712988!
@time-b!
;; 1403691714156!
http://bit.ly/reagi
Behaviours to Event Streams
http://bit.ly/reagi
(def time-e (r/sample 1000 time-b))!
!
(->> time-e (r/map println))!
;; t + 1 sec!
;; 1403692132586!
;; t + 2 sec:!
;; 1403692133587!
Combinators
(-> (Observable/return 42)!
(.map #(* % 2))!
(.subscribe println))!
!
;; 84!
!
(-> (Observable/from [10 20 30])!
(.map #(* % 2))!
(.reduce +)!
(.subscribe println))!
!
;; 120!
Combinators: flatMap /
selectMany
(defn project-range [n]!
(Rx.Observable/return (range n)))!
!
(-> (Observable/from [1 2 3])!
(.selectMany project-range)!
(.subscribe (rx/fn* println)))!
!
;; 0!
;; 0!
;; 1!
;; 0!
;; 1!
;; 2!
?
(Observable/from [1 2 3])
1 2 3
(-> (Observable/from [1 2 3])!
(.selectMany project-range)!
…)
(project-range 2)
0 1
(project-range 1)
0
(project-range 3)
0 1 2
0 0 1 0 1 2
What about network IO?
‣ Callback hell :(
‣ Clojure promises don’t compose
‣ Promises in JS are slightly better but have limitations
‣ They work well for a single level of values
‣ However are a poor composition mechanism
‣ What if we have a series of values that changes over time?
Demo: Simple polling app
This is what the server gives us
{:id 7!
:question "Which is the best music style?"!
:results {:a 10!
:b 47!
:c 17}}!
And this is what we want
‣ Render results

‣ Continuously poll server every 2 secs

‣ If current question is the same as the previous one update results; 

Otherwise:

‣ Stop polling;

‣ Display countdown message;

‣ Render new question and results;

‣ Restart polling;
The core idea
First, we need to turn the results into a stream
4 3 3 2 1 1
So we duplicate the stream, skipping one element
4 3 3 2 1 1
5 4 3 3 2 1
(skip 1)
Finally, we zip the streams
4 3 3 2 1 1
5 4 3 3 2 1
zip
[5,4] [4,3] [3,3] [3,2] [2,1] [1,1]
The core idea, in code
(defn results-observable!
"Returns an Observable that yields server-side questions/results"!
[]!
(.create js/Rx.Observable!
(fn [observer]!
(srm/rpc!
(poll-results) [resp]!
(.onNext observer resp))!
(fn [] (.log js/console "Disposed")))))
The core idea, in code
(def results-connectable!
"Zips results-observable with itself, but shifted by 1.!
This simulates a 'buffer' or 'window' of results"!
(let [obs (-> js/Rx.Observable!
(.interval 2000)!
(.selectMany results-observable)!
(.publish)!
(.refCount))!
obs-1 (.skip obs 1)]!
(.zip obs obs-1 (fn [prev curr]!
{:prev prev!
:curr curr}))))!
Turn results into a
stream
The core idea, in code
(def results-connectable!
"Zips results-observable with itself, but shifted by 1.!
This simulates a 'buffer' or 'window' of results"!
(let [obs (-> js/Rx.Observable!
(.interval 2000)!
(.selectMany results-observable)!
(.publish)!
(.refCount))!
obs-1 (.skip obs 1)]!
(.zip obs obs-1 (fn [prev curr]!
{:prev prev!
:curr curr}))))!
Clone stream, skip
one
The core idea, in code
(def results-connectable!
"Zips results-observable with itself, but shifted by 1.!
This simulates a 'buffer' or 'window' of results"!
(let [obs (-> js/Rx.Observable!
(.interval 2000)!
(.selectMany results-observable))!
obs-1 (.skip obs 1)]!
(.zip obs obs-1 (fn [prev curr]!
{:prev prev!
:curr curr}))))!
Zip them together
Can we do better?
Buffering
(def results-buffer!
"Returns an Observable with results buffered into a 2-element vector"!
(-> js/Rx.Observable!
(.interval 2000)!
(.selectMany results-observable)!
(.bufferWithCount 2)))!
Live Demo
"FRP is about handling time-varying
values like they were regular values" -
Haskell Wiki
"FRP is about handling time-varying
values like they were regular values" -
Haskell Wiki
It also applies to FRP-inspired systems
Why not use core.async?
Previously, with CES
(-> (Observable/from [10 20 30])!
(.map (rx/fn [v] (* v 2)))!
(.reduce (rx/fn* +)!
(.subscribe (rx/fn* println)))))
With core.async
(defn from-array [coll]!
(let [stream-c (chan)]!
(go (doseq [n coll]!
(>! stream-c n))!
(close! stream-c))!
stream-c))!
!
(def c (->> (async-from [10 20 30])!
(a/map< #(* % 2))!
(a/reduce + 0)))!
!
(go-loop []!
(when-let [v (<! c)]!
(println v)!
(recur)))!
Multiple subscribers with CES
(def sum-of-squares (-> (Observable/from [10 20 30])!
(.map (rx/fn [v] (* v 2)))!
(.reduce (rx/fn* +))))!
!
!
!
(.subscribe sum-of-squares (rx/action* println)) ;; 120!
(.subscribe sum-of-squares (rx/action* println)) ;; 120
Multiple subscribers with core.async [1/3]
(def in (chan))!
(def sum-of-squares (->> in!
(a/map< #(* % 2))!
(a/reduce + 0)))!
Multiple subscribers with core.async [2/3]
(def publication (pub sum-of-squares (fn [_] :n)))!
!
(def sub-1 (chan))!
(def sub-2 (chan))!
!
(sub publication :n sub-1)!
(sub publication :n sub-2)
Multiple subscribers with core.async [3/3]
(go (doseq [n [10 20 30]]!
(>! in n))!
(close! in))!
!
(go-loop []!
(when-let [v (<! sub-1)]!
(prn v)!
(recur))) ;; 120!
!
(go-loop []!
(when-let [v (<! sub-2)]!
(prn v)!
(recur))) ;; 120
core.async operates at a lower level of
abstraction
it is however a great foundation for a
FRP-inspired framework
Reagi - shown earlier - is built on top of
core.async
http://bit.ly/reagi
Bonus example: a reactive API to
AWS
Bonus example: a reactive API to
AWS
‣ Retrieve list of resources from a stack
(CloudFormation.describeStackResources)
‣ For each EC2 Instance, call EC2.describeInstances to retrieve status
‣ For each RDS Instance, call RDS.describeDBInstances to retrieve status
‣ Merge results and display
Step 1: turn api calls into streams
(defn resources-stream [stack-name]!
(.create js/Rx.Observable!
(fn [observer]!
(.describeStackResources js/cloudFormation #js {"StackName" : stackName}!
(fn [err data]!
(if err!
(.onError observer err)!
(doseq [resource data]!
(.onNext observer resource))!
(.onCompleted observer)))!
(fn [] (.log js/console "Disposed")))))
Step 1: turn api calls into streams
(defn ec2-instance-stream [resource-ids]!
(.create js/Rx.Observable!
(fn [observer]!
(.describeInstaces js/ec2 #js {"InstanceIds" resource-ids}!
(fn [err data]!
(if err!
(.onError observer err)!
(doseq [instance data]!
(.onNext observer instance)))!
(.onCompleted observer)))!
(fn [] (.log js/console "Disposed")))))!
Step 1: turn api calls into streams
(defn rds-instance-stream [resource-id]!
(.create js/Rx.Observable!
(fn [observer]!
(.describeDBInstances js/rds #js {"DBInstanceIdentifier" resource-id}!
(fn [err data]!
(if err!
(.onError observer err)!
(.onNext observer data))!
(.onCompleted observer)))!
(fn [] (.log js/console "Disposed")))))
Step 2: transform the different API
responses into a common output
(def resources (resourcesStream "my-stack"))!
!
(def ec2-data (-> resources!
(.filter ec2?)!
(.map :resource-id)!
(.flatMap ec2-instance-stream)!
(.map (fn [data] {:instance-id ...!
:status ...}))))!
!
(def rds-data (-> resources!
(.filter rds?)!
(.map :resource-id)!
(.flatMap rds-instance-stream)!
(.map (fn [data] {:instance-id ...!
:status ...}))))!
Step 3: merge results and update UI
(-> ec2-data!
(.merge rds-data)!
(.reduce conj [])!
(.subscribe (fn [data] (update-interface ...))))
Easy to reason about, maintain
and test
References
!
‣ Conal Elliott “Functional Reactive Animation” paper: http://bit.ly/conal-frp
!
FRP-inspired frameworks:
‣ Reagi: http://bit.ly/reagi
‣ RxJS: http://bit.ly/rxjs-github
‣ RxJava: http://bit.ly/rxjava-github
‣ Bacon.js: https://github.com/baconjs/bacon.js
!
FRP implementations:
‣ Reactive Banana: http://www.haskell.org/haskellwiki/Reactive-banana
‣ Elm: http://elm-lang.org/
‣ NetWire: http://www.haskell.org/haskellwiki/Netwire
Thanks!
Questions?
Leonardo Borges
@leonardo_borges
www.leonardoborges.com
www.thoughtworks.com

Contenu connexe

Tendances

Tendances (20)

Unit 07: Design Patterns and Frameworks (1/3)
Unit 07: Design Patterns and Frameworks (1/3)Unit 07: Design Patterns and Frameworks (1/3)
Unit 07: Design Patterns and Frameworks (1/3)
 
Asp.net.
Asp.net.Asp.net.
Asp.net.
 
중니어의 고뇌: 1인분 개발자, 다음을 찾아서
중니어의 고뇌: 1인분 개발자, 다음을 찾아서중니어의 고뇌: 1인분 개발자, 다음을 찾아서
중니어의 고뇌: 1인분 개발자, 다음을 찾아서
 
Android studio 2.0: default project structure
Android studio 2.0: default project structureAndroid studio 2.0: default project structure
Android studio 2.0: default project structure
 
What’s New in Spring Data MongoDB
What’s New in Spring Data MongoDBWhat’s New in Spring Data MongoDB
What’s New in Spring Data MongoDB
 
Secure Software Development with 3rd Party Dependencies
Secure Software Development with 3rd Party DependenciesSecure Software Development with 3rd Party Dependencies
Secure Software Development with 3rd Party Dependencies
 
IBM: Hey FIDO, Meet Passkey!.pptx
IBM: Hey FIDO, Meet Passkey!.pptxIBM: Hey FIDO, Meet Passkey!.pptx
IBM: Hey FIDO, Meet Passkey!.pptx
 
Metabase avec des chatons
Metabase avec des chatonsMetabase avec des chatons
Metabase avec des chatons
 
CA API Gateway: Web API and Application Security
CA API Gateway: Web API and Application SecurityCA API Gateway: Web API and Application Security
CA API Gateway: Web API and Application Security
 
Future-proofing Authentication with Passkeys
Future-proofing Authentication with PasskeysFuture-proofing Authentication with Passkeys
Future-proofing Authentication with Passkeys
 
REST API 설계
REST API 설계REST API 설계
REST API 설계
 
PHP Cookies and Sessions
PHP Cookies and SessionsPHP Cookies and Sessions
PHP Cookies and Sessions
 
Introdução do DEVSECOPS
Introdução do DEVSECOPSIntrodução do DEVSECOPS
Introdução do DEVSECOPS
 
Worksoft certify online training basic demo tutorial
Worksoft certify online training basic demo tutorialWorksoft certify online training basic demo tutorial
Worksoft certify online training basic demo tutorial
 
Les operations documentaires
Les operations documentairesLes operations documentaires
Les operations documentaires
 
Computer virus
Computer virusComputer virus
Computer virus
 
ワードウルフ
ワードウルフワードウルフ
ワードウルフ
 
Big picture of category theory in scala with deep dive into contravariant and...
Big picture of category theory in scala with deep dive into contravariant and...Big picture of category theory in scala with deep dive into contravariant and...
Big picture of category theory in scala with deep dive into contravariant and...
 
ASP.NET Basics
ASP.NET Basics ASP.NET Basics
ASP.NET Basics
 
Antivirus
AntivirusAntivirus
Antivirus
 

En vedette

En vedette (20)

Eclipse Modeling Framework (EMF) and Graphical Modeling Framework (GMF)
Eclipse Modeling Framework (EMF) and Graphical Modeling Framework (GMF)Eclipse Modeling Framework (EMF) and Graphical Modeling Framework (GMF)
Eclipse Modeling Framework (EMF) and Graphical Modeling Framework (GMF)
 
Functional Reactive Programming with RxJS
Functional Reactive Programming with RxJSFunctional Reactive Programming with RxJS
Functional Reactive Programming with RxJS
 
Rx- Reactive Extensions for .NET
Rx- Reactive Extensions for .NETRx- Reactive Extensions for .NET
Rx- Reactive Extensions for .NET
 
A Quick Intro to ReactiveX
A Quick Intro to ReactiveXA Quick Intro to ReactiveX
A Quick Intro to ReactiveX
 
Functional Reactive Programming
Functional Reactive ProgrammingFunctional Reactive Programming
Functional Reactive Programming
 
The algebra of library design
The algebra of library designThe algebra of library design
The algebra of library design
 
Functional programming in C#
Functional programming in C#Functional programming in C#
Functional programming in C#
 
What is Reactive programming?
What is Reactive programming?What is Reactive programming?
What is Reactive programming?
 
Functional Programming with C#
Functional Programming with C#Functional Programming with C#
Functional Programming with C#
 
ASP.NET MVC Core
ASP.NET MVC CoreASP.NET MVC Core
ASP.NET MVC Core
 
Reactive Programming with JavaScript
Reactive Programming with JavaScriptReactive Programming with JavaScript
Reactive Programming with JavaScript
 
Functional Programming in C# and F#
Functional Programming in C# and F#Functional Programming in C# and F#
Functional Programming in C# and F#
 
Functional Reactive Programming in Javascript
Functional Reactive Programming in JavascriptFunctional Reactive Programming in Javascript
Functional Reactive Programming in Javascript
 
02 - [ASP.NET Core] ASP.NET Core MVC
02 - [ASP.NET Core] ASP.NET Core MVC 02 - [ASP.NET Core] ASP.NET Core MVC
02 - [ASP.NET Core] ASP.NET Core MVC
 
Frisby Api automation
Frisby Api automationFrisby Api automation
Frisby Api automation
 
Microservices: Architecture for the Real-time Organization
Microservices: Architecture for the Real-time OrganizationMicroservices: Architecture for the Real-time Organization
Microservices: Architecture for the Real-time Organization
 
Real-Time Distributed and Reactive Systems with Apache Kafka and Apache Accumulo
Real-Time Distributed and Reactive Systems with Apache Kafka and Apache AccumuloReal-Time Distributed and Reactive Systems with Apache Kafka and Apache Accumulo
Real-Time Distributed and Reactive Systems with Apache Kafka and Apache Accumulo
 
Practical Medium Data Analytics with Python (10 Things I Hate About pandas, P...
Practical Medium Data Analytics with Python (10 Things I Hate About pandas, P...Practical Medium Data Analytics with Python (10 Things I Hate About pandas, P...
Practical Medium Data Analytics with Python (10 Things I Hate About pandas, P...
 
RxJS - The Reactive Extensions for JavaScript
RxJS - The Reactive Extensions for JavaScriptRxJS - The Reactive Extensions for JavaScript
RxJS - The Reactive Extensions for JavaScript
 
Reactive Extensions for JavaScript
Reactive Extensions for JavaScriptReactive Extensions for JavaScript
Reactive Extensions for JavaScript
 

Similaire à Functional Reactive Programming / Compositional Event Systems

Lock? We don't need no stinkin' locks!
Lock? We don't need no stinkin' locks!Lock? We don't need no stinkin' locks!
Lock? We don't need no stinkin' locks!
Michael Barker
 
The Evolution of Async-Programming on .NET Platform (.Net China, C#)
The Evolution of Async-Programming on .NET Platform (.Net China, C#)The Evolution of Async-Programming on .NET Platform (.Net China, C#)
The Evolution of Async-Programming on .NET Platform (.Net China, C#)
jeffz
 

Similaire à Functional Reactive Programming / Compositional Event Systems (20)

Incredible Machine with Pipelines and Generators
Incredible Machine with Pipelines and GeneratorsIncredible Machine with Pipelines and Generators
Incredible Machine with Pipelines and Generators
 
Golang Performance : microbenchmarks, profilers, and a war story
Golang Performance : microbenchmarks, profilers, and a war storyGolang Performance : microbenchmarks, profilers, and a war story
Golang Performance : microbenchmarks, profilers, and a war story
 
Beyond Breakpoints: A Tour of Dynamic Analysis
Beyond Breakpoints: A Tour of Dynamic AnalysisBeyond Breakpoints: A Tour of Dynamic Analysis
Beyond Breakpoints: A Tour of Dynamic Analysis
 
JVMLS 2016. Coroutines in Kotlin
JVMLS 2016. Coroutines in KotlinJVMLS 2016. Coroutines in Kotlin
JVMLS 2016. Coroutines in Kotlin
 
Frege - consequently functional programming for the JVM
Frege - consequently functional programming for the JVMFrege - consequently functional programming for the JVM
Frege - consequently functional programming for the JVM
 
ClojureScript loves React, DomCode May 26 2015
ClojureScript loves React, DomCode May 26 2015ClojureScript loves React, DomCode May 26 2015
ClojureScript loves React, DomCode May 26 2015
 
Building Hermetic Systems (without Docker)
Building Hermetic Systems (without Docker)Building Hermetic Systems (without Docker)
Building Hermetic Systems (without Docker)
 
Locks? We Don't Need No Stinkin' Locks - Michael Barker
Locks? We Don't Need No Stinkin' Locks - Michael BarkerLocks? We Don't Need No Stinkin' Locks - Michael Barker
Locks? We Don't Need No Stinkin' Locks - Michael Barker
 
Lock? We don't need no stinkin' locks!
Lock? We don't need no stinkin' locks!Lock? We don't need no stinkin' locks!
Lock? We don't need no stinkin' locks!
 
ECMAScript 6
ECMAScript 6ECMAScript 6
ECMAScript 6
 
The Evolution of Async-Programming on .NET Platform (.Net China, C#)
The Evolution of Async-Programming on .NET Platform (.Net China, C#)The Evolution of Async-Programming on .NET Platform (.Net China, C#)
The Evolution of Async-Programming on .NET Platform (.Net China, C#)
 
Continuation Passing Style and Macros in Clojure - Jan 2012
Continuation Passing Style and Macros in Clojure - Jan 2012Continuation Passing Style and Macros in Clojure - Jan 2012
Continuation Passing Style and Macros in Clojure - Jan 2012
 
JS everywhere 2011
JS everywhere 2011JS everywhere 2011
JS everywhere 2011
 
ClojureScript interfaces to React
ClojureScript interfaces to ReactClojureScript interfaces to React
ClojureScript interfaces to React
 
Progscon 2017: Taming the wild fronteer - Adventures in Clojurescript
Progscon 2017: Taming the wild fronteer - Adventures in ClojurescriptProgscon 2017: Taming the wild fronteer - Adventures in Clojurescript
Progscon 2017: Taming the wild fronteer - Adventures in Clojurescript
 
Exploring Clojurescript
Exploring ClojurescriptExploring Clojurescript
Exploring Clojurescript
 
FireWorks workflow software
FireWorks workflow softwareFireWorks workflow software
FireWorks workflow software
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJS
 
WebAssembly. Neither Web Nor Assembly, All Revolutionary
WebAssembly. Neither Web Nor Assembly, All RevolutionaryWebAssembly. Neither Web Nor Assembly, All Revolutionary
WebAssembly. Neither Web Nor Assembly, All Revolutionary
 
React native
React nativeReact native
React native
 

Plus de Leonardo Borges

Clouds against the Floods (RubyConfBR2011)
Clouds against the Floods (RubyConfBR2011) Clouds against the Floods (RubyConfBR2011)
Clouds against the Floods (RubyConfBR2011)
Leonardo Borges
 

Plus de Leonardo Borges (19)

Realtime collaboration with Clojure - EuroClojure - Barcelona, 2015
Realtime collaboration with Clojure - EuroClojure - Barcelona, 2015Realtime collaboration with Clojure - EuroClojure - Barcelona, 2015
Realtime collaboration with Clojure - EuroClojure - Barcelona, 2015
 
Parametricity - #cljsyd - May, 2015
Parametricity - #cljsyd - May, 2015Parametricity - #cljsyd - May, 2015
Parametricity - #cljsyd - May, 2015
 
From Java to Parellel Clojure - Clojure South 2019
From Java to Parellel Clojure - Clojure South 2019From Java to Parellel Clojure - Clojure South 2019
From Java to Parellel Clojure - Clojure South 2019
 
Futures e abstração - QCon São Paulo 2015
Futures e abstração - QCon São Paulo 2015Futures e abstração - QCon São Paulo 2015
Futures e abstração - QCon São Paulo 2015
 
High Performance web apps in Om, React and ClojureScript
High Performance web apps in Om, React and ClojureScriptHigh Performance web apps in Om, React and ClojureScript
High Performance web apps in Om, React and ClojureScript
 
Programação functional reativa: lidando com código assíncrono
Programação functional reativa: lidando com código assíncronoProgramação functional reativa: lidando com código assíncrono
Programação functional reativa: lidando com código assíncrono
 
Monads in Clojure
Monads in ClojureMonads in Clojure
Monads in Clojure
 
Clojure Macros Workshop: LambdaJam 2013 / CUFP 2013
Clojure Macros Workshop: LambdaJam 2013 / CUFP 2013Clojure Macros Workshop: LambdaJam 2013 / CUFP 2013
Clojure Macros Workshop: LambdaJam 2013 / CUFP 2013
 
Intro to Clojure's core.async
Intro to Clojure's core.asyncIntro to Clojure's core.async
Intro to Clojure's core.async
 
Functional Reactive Programming in Clojurescript
Functional Reactive Programming in ClojurescriptFunctional Reactive Programming in Clojurescript
Functional Reactive Programming in Clojurescript
 
Clojure/West 2013 in 30 mins
Clojure/West 2013 in 30 minsClojure/West 2013 in 30 mins
Clojure/West 2013 in 30 mins
 
Clojure Reducers / clj-syd Aug 2012
Clojure Reducers / clj-syd Aug 2012Clojure Reducers / clj-syd Aug 2012
Clojure Reducers / clj-syd Aug 2012
 
The many facets of code reuse in JavaScript
The many facets of code reuse in JavaScriptThe many facets of code reuse in JavaScript
The many facets of code reuse in JavaScript
 
Heroku addons development - Nov 2011
Heroku addons development - Nov 2011Heroku addons development - Nov 2011
Heroku addons development - Nov 2011
 
Clouds against the Floods (RubyConfBR2011)
Clouds against the Floods (RubyConfBR2011) Clouds against the Floods (RubyConfBR2011)
Clouds against the Floods (RubyConfBR2011)
 
Clouds Against the Floods
Clouds Against the FloodsClouds Against the Floods
Clouds Against the Floods
 
Arel in Rails 3
Arel in Rails 3Arel in Rails 3
Arel in Rails 3
 
Testing with Spring
Testing with SpringTesting with Spring
Testing with Spring
 
JRuby in The Enterprise
JRuby in The EnterpriseJRuby in The Enterprise
JRuby in The Enterprise
 

Dernier

Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Victor Rentea
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 

Dernier (20)

How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
Vector Search -An Introduction in Oracle Database 23ai.pptx
Vector Search -An Introduction in Oracle Database 23ai.pptxVector Search -An Introduction in Oracle Database 23ai.pptx
Vector Search -An Introduction in Oracle Database 23ai.pptx
 
Six Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal OntologySix Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal Ontology
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with Milvus
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
 
Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)
 
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdfRising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectors
 

Functional Reactive Programming / Compositional Event Systems

  • 1. Taming Asynchronous Workflows with Functional Reactive Programming EuroClojure - Kraków, 2014 Leonardo Borges @leonardo_borges www.leonardoborges.com www.thoughtworks.com
  • 2. About ‣ ThoughtWorker ‣ Functional Programming & Clojure advocate ‣ Founder of the Sydney Clojure User Group ‣ Currently writing “Clojure Reactive Programming”
  • 3. Taming Asynchronous Workflows with Functional Reactive Programming
  • 4. Taming Asynchronous Workflows with Functional Reactive Programming Compositional Event Systems
  • 8. There are only two hard things in Computer Science: cache invalidation and naming things. - Phil Karlton :)
  • 9. Ok, so what’s the difference?
  • 10. ‣ Created in 1997 by Conal Elliott for the reactive animations framework Fran, in Haskell ‣ Since then other implementations have appeared: reactive-banana, NetWire, Sodium (all in Haskell) ‣ And then FRP-inspired ones: Rx[.NET | Java | JS], Baconjs, reagi (Clojurescript) ‣ Main abstractions: Behaviors e Events More about FRP
  • 11. ‣ Created in 1997 by Conal Elliott for the reactive animations framework Fran, in Haskell ‣ Since then other implementations have appeared: reactive-banana, NetWire, Sodium (all in Haskell) ‣ And then FRP-inspired ones: Rx[.NET | Java | JS], Baconjs, reagi (Clojure[script]) ‣ Main abstractions: Behaviors e Events ‣ Traditionally defined as: type Behavior a = [Time] -> [a]! type Event a = [Time] -> [Maybe a] More about FRP
  • 12. We’ll be focusing on Compositional Event Systems
  • 14. Imperative programming describes computations as a series of actions which modify program state var result = 1;! numbers.forEach(function(n){! if(n % 2 === 0) {! result *= n;! }! });! console.log( result );! // 8! var numbers = [1,2,3,4,5]; Requires a variable to store state
  • 15. var result = 1;! numbers.forEach(function(n){! if(n % 2 === 0) {! result *= n;! }! });! console.log( result );! // 8! var numbers = [1,2,3,4,5]; We iterate over the array Imperative programming describes computations as a series of actions which modify program state
  • 16. var result = 1;! numbers.forEach(function(n){! if(n % 2 === 0) {! result *= n;! }! });! console.log( result );! // 8! var numbers = [1,2,3,4,5]; And then we filter the items… Imperative programming describes computations as a series of actions which modify program state
  • 17. var result = 1;! numbers.forEach(function(n){! if(n % 2 === 0) {! result *= n;! }! });! console.log( result );! // 8! var numbers = [1,2,3,4,5]; …and perform the multiplication in the same function Imperative programming describes computations as a series of actions which modify program state
  • 18. (def numbers [1 2 3 4 5])! ! (def result! (->> numbers! (filter even?)! (reduce *)))! ! (prn result)! ! ;; 8 In functional programming, we describe what we want to do but not how we want it done
  • 19. That is, there are no variables with local state and we get better re-use from single purpose functions
  • 20. Compositional Event Systems brings the same principle to values we work with daily: DOM events (clicks, key presses, mouse movement), Ajax calls…
  • 21. Let’s look at an example
  • 22. Game movements in Javascript var JUMP = 38, CROUCH = 40,! LEFT = 37, RIGHT = 39,! FIRE = 32;! ! function goRight (){! $(‘h1').html("Going right...");! }! ! function goLeft (){! $(‘h1').html("Going left...");! }! ! function jump (){! $('h1').html("Jumping...");! }! ! function crouch (){! $('h1').html("Crouching...");! }! ! function fire (){! $('h1').html("Firing...");! }
  • 23. Game movements in Javascript $(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;! };! });
  • 24. We now have limitations similar to the multiplication example
  • 25. Let us think key presses as a list of keys over a period of time
  • 26. This leads to the following solution
  • 27. Reactive game movements (def UP 38) (def RIGHT 39)! (def DOWN 40) (def LEFT 37)! (def FIRE 32) (def PAUSE 80)! ! ! (def source (.fromEvent js/Rx.Observable js/window "keyup"));! ! (-> source (.filter #(= UP %)) (.subscribe jump))! (-> source (.filter #(= DOWN %)) (.subscribe crouch))! (-> source (.filter #(= RIGHT %)) (.subscribe go-right))! (-> source (.filter #(= LEFT %)) (.subscribe go-left))! (-> source (.filter #(= FIRE %)) (.subscribe fire))! http://bit.ly/rxjava-github http://bit.ly/rxjs-github
  • 28. Reactive game movements (def UP 38) (def RIGHT 39)! (def DOWN 40) (def LEFT 37)! (def FIRE 32) (def PAUSE 80)! ! ! (def source (.fromEvent js/Rx.Observable js/window "keyup"));! ! (-> source (.filter #(= UP %)) (.subscribe jump))! (-> source (.filter #(= DOWN %)) (.subscribe crouch))! (-> source (.filter #(= RIGHT %)) (.subscribe go-right))! (-> source (.filter #(= LEFT %)) (.subscribe go-left))! (-> source (.filter #(= FIRE %)) (.subscribe fire))! http://bit.ly/rxjava-github http://bit.ly/rxjs-github
  • 29. Behaviours ;; behavior examples! (def time-b (r/behavior (System/currentTimeMillis)))! @time-b! ;; 1403691712988! @time-b! ;; 1403691714156! http://bit.ly/reagi
  • 30. Behaviours to Event Streams http://bit.ly/reagi (def time-e (r/sample 1000 time-b))! ! (->> time-e (r/map println))! ;; t + 1 sec! ;; 1403692132586! ;; t + 2 sec:! ;; 1403692133587!
  • 31. Combinators (-> (Observable/return 42)! (.map #(* % 2))! (.subscribe println))! ! ;; 84! ! (-> (Observable/from [10 20 30])! (.map #(* % 2))! (.reduce +)! (.subscribe println))! ! ;; 120!
  • 32. Combinators: flatMap / selectMany (defn project-range [n]! (Rx.Observable/return (range n)))! ! (-> (Observable/from [1 2 3])! (.selectMany project-range)! (.subscribe (rx/fn* println)))! ! ;; 0! ;; 0! ;; 1! ;; 0! ;; 1! ;; 2!
  • 33. ?
  • 35. (-> (Observable/from [1 2 3])! (.selectMany project-range)! …) (project-range 2) 0 1 (project-range 1) 0 (project-range 3) 0 1 2 0 0 1 0 1 2
  • 36. What about network IO? ‣ Callback hell :( ‣ Clojure promises don’t compose ‣ Promises in JS are slightly better but have limitations ‣ They work well for a single level of values ‣ However are a poor composition mechanism ‣ What if we have a series of values that changes over time?
  • 38. This is what the server gives us {:id 7! :question "Which is the best music style?"! :results {:a 10! :b 47! :c 17}}!
  • 39. And this is what we want ‣ Render results ‣ Continuously poll server every 2 secs ‣ If current question is the same as the previous one update results; Otherwise: ‣ Stop polling; ‣ Display countdown message; ‣ Render new question and results; ‣ Restart polling;
  • 41. First, we need to turn the results into a stream 4 3 3 2 1 1
  • 42. So we duplicate the stream, skipping one element 4 3 3 2 1 1 5 4 3 3 2 1 (skip 1)
  • 43. Finally, we zip the streams 4 3 3 2 1 1 5 4 3 3 2 1 zip [5,4] [4,3] [3,3] [3,2] [2,1] [1,1]
  • 44. The core idea, in code (defn results-observable! "Returns an Observable that yields server-side questions/results"! []! (.create js/Rx.Observable! (fn [observer]! (srm/rpc! (poll-results) [resp]! (.onNext observer resp))! (fn [] (.log js/console "Disposed")))))
  • 45. The core idea, in code (def results-connectable! "Zips results-observable with itself, but shifted by 1.! This simulates a 'buffer' or 'window' of results"! (let [obs (-> js/Rx.Observable! (.interval 2000)! (.selectMany results-observable)! (.publish)! (.refCount))! obs-1 (.skip obs 1)]! (.zip obs obs-1 (fn [prev curr]! {:prev prev! :curr curr}))))! Turn results into a stream
  • 46. The core idea, in code (def results-connectable! "Zips results-observable with itself, but shifted by 1.! This simulates a 'buffer' or 'window' of results"! (let [obs (-> js/Rx.Observable! (.interval 2000)! (.selectMany results-observable)! (.publish)! (.refCount))! obs-1 (.skip obs 1)]! (.zip obs obs-1 (fn [prev curr]! {:prev prev! :curr curr}))))! Clone stream, skip one
  • 47. The core idea, in code (def results-connectable! "Zips results-observable with itself, but shifted by 1.! This simulates a 'buffer' or 'window' of results"! (let [obs (-> js/Rx.Observable! (.interval 2000)! (.selectMany results-observable))! obs-1 (.skip obs 1)]! (.zip obs obs-1 (fn [prev curr]! {:prev prev! :curr curr}))))! Zip them together
  • 48. Can we do better?
  • 49. Buffering (def results-buffer! "Returns an Observable with results buffered into a 2-element vector"! (-> js/Rx.Observable! (.interval 2000)! (.selectMany results-observable)! (.bufferWithCount 2)))!
  • 51. "FRP is about handling time-varying values like they were regular values" - Haskell Wiki
  • 52. "FRP is about handling time-varying values like they were regular values" - Haskell Wiki It also applies to FRP-inspired systems
  • 53. Why not use core.async?
  • 54. Previously, with CES (-> (Observable/from [10 20 30])! (.map (rx/fn [v] (* v 2)))! (.reduce (rx/fn* +)! (.subscribe (rx/fn* println)))))
  • 55. With core.async (defn from-array [coll]! (let [stream-c (chan)]! (go (doseq [n coll]! (>! stream-c n))! (close! stream-c))! stream-c))! ! (def c (->> (async-from [10 20 30])! (a/map< #(* % 2))! (a/reduce + 0)))! ! (go-loop []! (when-let [v (<! c)]! (println v)! (recur)))!
  • 56. Multiple subscribers with CES (def sum-of-squares (-> (Observable/from [10 20 30])! (.map (rx/fn [v] (* v 2)))! (.reduce (rx/fn* +))))! ! ! ! (.subscribe sum-of-squares (rx/action* println)) ;; 120! (.subscribe sum-of-squares (rx/action* println)) ;; 120
  • 57. Multiple subscribers with core.async [1/3] (def in (chan))! (def sum-of-squares (->> in! (a/map< #(* % 2))! (a/reduce + 0)))!
  • 58. Multiple subscribers with core.async [2/3] (def publication (pub sum-of-squares (fn [_] :n)))! ! (def sub-1 (chan))! (def sub-2 (chan))! ! (sub publication :n sub-1)! (sub publication :n sub-2)
  • 59. Multiple subscribers with core.async [3/3] (go (doseq [n [10 20 30]]! (>! in n))! (close! in))! ! (go-loop []! (when-let [v (<! sub-1)]! (prn v)! (recur))) ;; 120! ! (go-loop []! (when-let [v (<! sub-2)]! (prn v)! (recur))) ;; 120
  • 60. core.async operates at a lower level of abstraction
  • 61. it is however a great foundation for a FRP-inspired framework
  • 62. Reagi - shown earlier - is built on top of core.async http://bit.ly/reagi
  • 63. Bonus example: a reactive API to AWS
  • 64. Bonus example: a reactive API to AWS ‣ Retrieve list of resources from a stack (CloudFormation.describeStackResources) ‣ For each EC2 Instance, call EC2.describeInstances to retrieve status ‣ For each RDS Instance, call RDS.describeDBInstances to retrieve status ‣ Merge results and display
  • 65. Step 1: turn api calls into streams (defn resources-stream [stack-name]! (.create js/Rx.Observable! (fn [observer]! (.describeStackResources js/cloudFormation #js {"StackName" : stackName}! (fn [err data]! (if err! (.onError observer err)! (doseq [resource data]! (.onNext observer resource))! (.onCompleted observer)))! (fn [] (.log js/console "Disposed")))))
  • 66. Step 1: turn api calls into streams (defn ec2-instance-stream [resource-ids]! (.create js/Rx.Observable! (fn [observer]! (.describeInstaces js/ec2 #js {"InstanceIds" resource-ids}! (fn [err data]! (if err! (.onError observer err)! (doseq [instance data]! (.onNext observer instance)))! (.onCompleted observer)))! (fn [] (.log js/console "Disposed")))))!
  • 67. Step 1: turn api calls into streams (defn rds-instance-stream [resource-id]! (.create js/Rx.Observable! (fn [observer]! (.describeDBInstances js/rds #js {"DBInstanceIdentifier" resource-id}! (fn [err data]! (if err! (.onError observer err)! (.onNext observer data))! (.onCompleted observer)))! (fn [] (.log js/console "Disposed")))))
  • 68. Step 2: transform the different API responses into a common output (def resources (resourcesStream "my-stack"))! ! (def ec2-data (-> resources! (.filter ec2?)! (.map :resource-id)! (.flatMap ec2-instance-stream)! (.map (fn [data] {:instance-id ...! :status ...}))))! ! (def rds-data (-> resources! (.filter rds?)! (.map :resource-id)! (.flatMap rds-instance-stream)! (.map (fn [data] {:instance-id ...! :status ...}))))!
  • 69. Step 3: merge results and update UI (-> ec2-data! (.merge rds-data)! (.reduce conj [])! (.subscribe (fn [data] (update-interface ...))))
  • 70. Easy to reason about, maintain and test
  • 71. References ! ‣ Conal Elliott “Functional Reactive Animation” paper: http://bit.ly/conal-frp ! FRP-inspired frameworks: ‣ Reagi: http://bit.ly/reagi ‣ RxJS: http://bit.ly/rxjs-github ‣ RxJava: http://bit.ly/rxjava-github ‣ Bacon.js: https://github.com/baconjs/bacon.js ! FRP implementations: ‣ Reactive Banana: http://www.haskell.org/haskellwiki/Reactive-banana ‣ Elm: http://elm-lang.org/ ‣ NetWire: http://www.haskell.org/haskellwiki/Netwire