2. About
‣ ThoughtWorker
‣ Functional Programming & Clojure
advocate
‣ Founder of the Sydney Clojure User
Group
‣ Currently writing “Clojure Reactive
Programming”
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
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…
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...");!
}
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
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