Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Functional Reactive Programming in JavaScript

Functional Reactive Programming in JavaScript

In this talk I give an intro to functional reactive programming with the Bacon.js library.

Josh Bassett

August 13, 2014
Tweet

More Decks by Josh Bassett

Other Decks in Programming

Transcript

  1. 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.
  2. 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).
  3. 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
  4. 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]
  5. 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).
  6. Map - Applies a function to every element in a

    data structure and returns a new data structure. - Array.prototype.map function in JS.
  7. Map function inc(a) { return a + 1 } [1,

    2, 3].map(inc) => [2, 3, 4]
  8. Filter - Filters every element in a data structure using

    predicate and returns a new data structure. - Array.prototype.filter function in JS.
  9. Filter function odd(a) { return a % 2 != 0

    } [1, 2, 3].filter(odd) => [1, 3]
  10. 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.
  11. Fold function add(a, b) { return a + b }


    [1, 2, 3].reduce(add, 0) => 6
 [1, 2, 3].reduceRight(add, 0) => 6
  12. Scan - Reduces a data structure by combining its elements

    using a binary function and returns the intermediate results. - Not implemented in native JS
  13. 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]
  14. 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.
  15. Streams - Models flow of data with streams. - A

    stream is a functional data structure (supports combinators like map, filter, fold, scan, etc).
  16. 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
  17. Buses var a = new Bacon.Bus() var b = new

    Bacon.Bus()
 a.log() a.plug(b)
 a.push(‘hello’) => hello
 b.push(‘world’) => world
  18. 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.
  19. Number Field var NumberFieldComponent = React.createClass({ render: function() { return

    ( <div> <span>{this.props.value}</span> <a onClick={this.handleUp}>⬆️</a> <a onClick={this.handleDown}>⬇️</a> </div> ); }, handleUp: function() { this.props.bus.push(‘up’); }, handleDown: function() { this.props.bus.push(‘down’); } });
  20. 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);
  21. 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.
  22. 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; }
  23. 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.
  24. Memory function Game(cards) { this.cards = cards; this.selectedCards = [];

    } function Card(shape) { this.shape = shape; this.disabled = false; }
  25. 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);
  26. 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; }
  27. 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; };