Functional
Reactive
Programming
Josh Bassett
in JavaScript
Slide 2
Slide 2 text
Why Should You Care?
- Writing code is easy (for a programmer).
- Understanding (fixing/changing) code is hard.
- There is inherent complexity in the way we
choose to solve a problem.
- FRP allows us to reduce the inherent
complexity of our code.
Slide 3
Slide 3 text
Functional Programming
Slide 4
Slide 4 text
Pure Functions
- Any function which always returns the same
output, given the same input.
- Calling the function does not cause any
observable side effects (e.g. mutation of
mutable objects).
Slide 5
Slide 5 text
Pure Functions
function add(a, b) { return a + b }
add(1, 2)
=> 3
var c = 1 // external state
function add(a, b) { return a + b + c }
add(1, 2)
=> 4
c = 2
add(1, 2) // same input
=> 5
Slide 6
Slide 6 text
Pure Functions
var a = [1, 2, 3]
a.slice(1)
=> [2, 3]
a
=> [1, 2, 3]
a.reverse()
=> [3, 2, 1]
a
=> [3, 2, 1]
Slide 7
Slide 7 text
Immutability
- An immutable object is an object whose state
can’t be changed.
- Can’t be enforced in JS, convention only.
- Use POJOs and copy them on update (e.g. use
React.addons.update function).
- Use a persistent data structure library (e.g. mori,
immutable).
Slide 8
Slide 8 text
Map
- Applies a function to every element in a data
structure and returns a new data structure.
- Array.prototype.map function in JS.
Slide 9
Slide 9 text
Map
function inc(a) { return a + 1 }
[1, 2, 3].map(inc)
=> [2, 3, 4]
Slide 10
Slide 10 text
Filter
- Filters every element in a data structure using
predicate and returns a new data structure.
- Array.prototype.filter function in JS.
Slide 11
Slide 11 text
Filter
function odd(a) { return a % 2 != 0 }
[1, 2, 3].filter(odd)
=> [1, 3]
Slide 12
Slide 12 text
Fold
- Reduces a data structure by combining its
elements using a binary function and returns a
single value.
- Array.prototype.reduce &
Array.prototype.reduceRight functions in JS.
Slide 13
Slide 13 text
Fold
function add(a, b) { return a + b }
[1, 2, 3].reduce(add, 0)
=> 6
[1, 2, 3].reduceRight(add, 0)
=> 6
Slide 14
Slide 14 text
Scan
- Reduces a data structure by combining its
elements using a binary function and returns
the intermediate results.
- Not implemented in native JS
Slide 15
Slide 15 text
Scan
function add(a, b) { return a + b }
[1, 2, 3].scan(add, 0)
=> [0, 1, 3, 6]
[1, 2, 3].scanRight(add, 0)
=> [6, 3, 1, 0]
Slide 16
Slide 16 text
Functional Reactive Programming
for great good
Slide 17
Slide 17 text
FRP
- Paradigm from FP community.
- Focus on behaviour over implementation.
- Models flow of data in a system using
functional data structures called streams.
- Avoids callback hell with FP combinators.
- Libraries available for many languages.
Slide 18
Slide 18 text
FRP in JavaScript
Slide 19
Slide 19 text
Streams
- Models flow of data with streams.
- A stream is a functional data structure
(supports combinators like map, filter, fold,
scan, etc).
Slide 20
Slide 20 text
Streams
var s = Bacon.fromEventTarget(document.body,
‘click’)
var s = $(‘#button’).asEventStream(‘click’)
s.onValue(function(v) { console.log(v) })
s.log()
Slide 21
Slide 21 text
Streams
function add(a, b) { return a + b }
var up = $(‘#up’).asEventStream(‘click’)
var down = $(‘#down’).asEventStream(‘click’)
var delta = up.map(1).merge(down.map(-1))
var sum = delta.scan(0, add)
sum.log()
=> 0
=> 1
=> 2
=> 1
=> etc
Slide 22
Slide 22 text
Properties
var property = stream.toProperty()
property.assign($(‘.label’), ‘text’)
Slide 23
Slide 23 text
Buses
var a = new Bacon.Bus()
var b = new Bacon.Bus()
a.log()
a.plug(b)
a.push(‘hello’)
=> hello
b.push(‘world’)
=> world
Slide 24
Slide 24 text
React & Bacon
Slide 25
Slide 25 text
Components
Slide 26
Slide 26 text
Components
- Instantiate the component with a reference to
the bus (via props).
- Component handles UI events and pushes
them onto the bus.
- The bus streams events from the UI back to
our app logic.
Slide 27
Slide 27 text
Number Field
Slide 28
Slide 28 text
Number Field
var NumberFieldComponent = React.createClass({
render: function() {
return (
Number Field
function add(a, b) { return a + b; }
var bus = new Bacon.Bus();
var up = bus.filter(function(e) {
return e === ‘up’;
});
var down = bus.filter(function(e) {
return e === ‘down’;
});
var delta = up.map(1).merge(down.map(-1));
var sum = delta.scan(0, add);
Slide 30
Slide 30 text
Number Field
sum.onValue(function(value) {
React.renderComponent(
NumberFieldComponent({
bus: bus,
value: value
}),
document.body
);
});
Slide 31
Slide 31 text
State Transformer
Slide 32
Slide 32 text
State Transformer
- A state transformer is a pure function which
takes a state and an input value as arguments
and returns a new state.
- It’s a binary function which can be scanned
over a stream of input events to produce a
stream of state objects.
Slide 33
Slide 33 text
State Transformer
var state = bus.scan(
new MyState(),
transformState
);
Slide 34
Slide 34 text
State Transformer
function transformState(state, input) {
if (input.type === 'a') {
state = state.foo(input.data);
} else if (input.type === 'b') {
state = state.bar(input.data);
}
return state;
}
Slide 35
Slide 35 text
App Pipeline
Slide 36
Slide 36 text
App Pipeline
- The bus streams input events from the UI.
- The state transformer is scanned over the
bus to produce a state object stream.
- The React.renderComponent function is
applied to the state object value to produce a
new VDOM.
Memory
var game = new Game(cards);
var bus = new Bacon.Bus(),
gameState = bus.scan(game, transformState),
selectedCards = gameState.map(‘.selectedCards');
var deselectCards = selectedCards
.filter(function(a) { return a.length >= 2; })
.map(function() { return {type: 'deselect-cards'};
})
.debounce(1000);
bus.plug(deselectCards);
Slide 40
Slide 40 text
Memory
function transformState(game, event) {
if (event.type === 'select-card') {
game = game.selectCard(event.card);
} else if (event.type === 'deselect-cards') {
game = game.deselectAllCards();
}
return game;
}
Slide 41
Slide 41 text
Memory
Game.prototype.selectCard = function(card) {
if (_.contains(this.selectedCards, card)) return
this;
if (this.selectedCards.length >= 2) return this;
var game = this.addSelectedCard(card);
if (game.selectedCardsAreMatching()) {
game = game.disableSelectedCards();
}
return game;
};