MVC to FRP

Gleb Bahmutov

My evolving approach to the front end application development

Gleb Bahmutov

JavaScript ninja, image processing expert, software quality fanatic

#CycleConf

work: Kensho

event analysis and statistics for financial markets

Boston and New York

Kensho dashboard app

data

Kensho dashboard app

Kensho dashboard app

finished?

$http.get(...).then(...)

Kensho dashboard app

finished?

$http.get(...).then(...)

.then(...)

* times

1. Event emitters are great at broadcasting without expecting an answer

2. Promises are great for single time actions

Kensho       Angular!

plain object

plain object

prototype

var outer = { foo: 'bar' }
var inner = Object.create(outer)

JavaScript prototypes

We are not describing a "blueprint" for making objects. We are making objects!

inner.foo // 'bar'

Angular scopes

function inner($scope) {
  $scope.foo = 42
}
<div ng-controller="inner">
  {{ foo }}
</div>

Angular code works with objects, not classes

Angular scopes are plain objects

function inner($scope) {
  $scope.foo = 42
  $scope.setFoo = function (v) {
    $scope.foo = v
  }
}
<div ng-controller="inner"
  ng-click="setFoo(10)">
  {{ foo }}
</div>

mixing in properties and methods

Methods without "this"

function inner($scope) {
  $scope.foo = 42
  $scope.setFoo = function (v) {
    $scope.foo = v
  }
}
$('div').scope().setFoo(2)
var set = $('div').scope().setFoo
set(100)

Methods that use "this"

Promise.resolve(42).then(console.log)

Error: Illegal invocation

log: function () {
  this.doSomething(...)
}

I want a framework that:

  • Builds on JavaScript strength
  • Avoids using object context "this"
  • Can manage async events

Kensho dashboard app

stream

stream operation

* times

stream

stream

stream

RxJS for the win!

stream

stream

RxJS like a boss

Start with ES5 Array operations: map, filter, some, reduce

synchronous callbacks, finite list

ES5 Arrays

synchronous callbacks, finite list

const out = [1, 2, 3]
    .map(x => x * 2)
    .filter(x > 3)
console.log(out) // [4, 6]

synchronous callbacks, finite list

result is returned from the call

const out = [1, 2, 3]
    .map(x => x * 2)
    .filter(x > 3)
console.log(out) // [4, 6]

ES5 Arrays

Reactive stream

asynchronous callbacks, infinite

Rx.Observable.from(...)
    .map(x => x * 2)
    .filter(x > 3)
    .buffer(...)
    

item is passed forward

sequence / time methods

If a tree falls in the forest and no one subscribes to tree_falls$

Rx.Observable.from(...)
    .map(x => x * 2)
    .filter(x > 3)
    .buffer(...)
    
function add(a, b) {
  return a + b
}
    .subscribe(...)
    
add(2, 3)

Some callback functions are better than others

[1, 2, 3].map(x => x * 2)
  .forEach(console.log)

Error: Illegal invocation

[1, 2, 3].map(x => x * 2)
  .forEach(console.log.bind(console))

Pure functions make the best callbacks

function double(x) {
  return x * 2
}
  1. Given the same input, will always return the same output.
  2. Produces no side effects.
  3. Relies on no external state.
function action(op, list) {
  return list.map(op)
}
// action(double, [1, 2, 3])
window.postMessage(double.toString())
const recreated = eval('(' + message + ')')

Pure functions make the best callbacks

function double(x) {
  return x * 2
}

Pure functions are the best

Simple to write

Simple to read

Simple to test

Pure functions are the best

function add(a, b) {
  return a + b
}
....
   ....
   const result = add(10, 4)
   ....
....

Referencial transparency 

Pure functions are the best

function add(a, b) {
  return a + b
}
....
   ....
   const result = 10 + 4
   ....
....

Referencial transparency 

Pure functions are the best

function add(a, b) {
  return a + b
}
....
   ....
   const result = 14
   ....
....

Referencial transparency 

Pure functions are the best

function expensive(x) {
  // do something that takes a while
}
....
   ....
   [1, 2, 3, 2, 2]
     .map(R.memoize(expensive))
   ....
....
// expensive(1), expensive(2), expensive(3)

Memoization

Pure functions are better than methods

var o = foo()
// hmm
o<method name>()
var fn = foo()
fn(...)

Using a method requires knowing about the object's structure (its API)

Using a function is simple - just call it.

Is fn pure?

function foo(a, b, c) {
   **** a *** {
     if (b < ****) {
     }
     *** x
   }
}

Is fn pure?

function foo(a, b, c) {
   var x = 3
   **** a *** {
     if (b < ****) {
     }
     *** x
   }
}

Is fn pure?

const x = 10
function foo(a, b, c) {
   **** a *** {
     if (b < ****) {
     }
     *** x
   }
}

Is fn pure?

function x(...) {} // pure
function foo(a, b, c) {
   **** a *** {
     if (b < ****) {
     }
     *** x
   }
}

Pure functions almost always use the input arguments only

function foo(a, b, c) {
   **** a *** {
     if (b < ****) {
     }
     *** c
   }
}

Wait, do I need to pass 10 arguments every time then?

Pure functions make new pure functions really quickly

function foo(a, b, c) { ... }
var fooA = foo.bind(null, valueA)
fooA(valueB, valueC)

Function signature design

function foo(a, b, c) { ... }
// (a, b, c) or (b, c, a) or ...

?

?

?

Place arguments most likely to be known first at the leftmost position

Takes advantage of the built-in JavaScript partial application

Partial appliction

function mul(a, b) { return a * b }
const by10 = mul.bind(null, 10)
by10(8) // 80

Curry: make any function unary

const R = require('ramda')
const mul = R.curry((a, b) => a * b)
// by10 = mul.bind(null, 10)
const by10 = mul(10)
by10(8) // 80

Other partial application types

  • From the right
  • Selective (leaving unfilled positions)
  • By name (like AngularJS)
  • Property inside options object

Pure unary functions are very simple to compose

f(g(h(x)))
// same as
const F = compose(f, g, h)
F(x)
const F = compose(f, g, h)
F(x)

pure

pure

Can a web application be pure?

Getting dirty

function main() {
  window.title = title
}
function controller($window) {
  $window.title = title
}

Pure function cop out

function main() {
  const title = 'hi'
  return function foo() {
    window.title = title
  }
}

// pure

// dirty

const app = main()
app()

// pure

// dirty

A little cleaner

function main(win) {
  const title = 'hi'
  return function foo() {
    win.title = title
  }
}

// pure

// dirty

const app = main()
app(window)

// pure

// dirty

Looks like CycleJs

function main({DOM}) {
  const stream$ = DOM.select(...)
  return {
    DOM: stream$
  }
}
Cycle.run(main, {
  DOM: makeDomDriver... 
})

// pure

// dirty

Looks like CycleJs

function main({DOM}) {
  const stream$ = DOM.select(...)
  return {
    DOM: stream$
  }
}
Cycle.run(main, {
  DOM: makeDomDriver... 
})

we have created streams from DOM to DOM - but they are NOT running yet

// DOM.stream$.subscribe(...)

Back to Kensho ng+Rx app

function inner($scope) {
  $scope.foo = 42
  $scope.setFoo = function (v) {
    $scope.foo = v
  }
}

+ plain objects without "this"

- very impure behavior

- performance, learning curve

Angular MVC code

Solve the reactivity part of the data flow a little, but not the app's purity

Solves the efficient DOM update problem, but not the (async) data flow

React was chosen to solve performance and component development problems

CycleJs is coming

because realtime

because Internet of Things (IoT)

New realtime backends: feathers, horizon need real time frontends

CycleJs wraps around realtime feeds perfectly

Watch my web app being used

function makeCodeCoverageDriver(serverUrl) {
  const ws = new WebSocket(serverUrl)
  return (outgoing$) => {
    outgoing$.subscribe(outgoing => ws.send(outgoing))
    return Rx.Observable.fromCallback(ws.onmessage)()
  }
}

Uses WebSocket or WebRTC data channel

Links are coming ...

MVC to FRP

JS => FP => Reactive => Pure => Realtime

Thank you

#CycleConf

MVC to FRP

By Gleb Bahmutov

MVC to FRP

Transition from the traditional Model-View-Controller application to Functional-Reactive-Programming. Presented at CycleConf 2016. Video at https://www.youtube.com/watch?v=-PCq4pXaDZw and all links are in blog post https://glebbahmutov.com/blog/cycle-conf/

  • 3,814
Loading comments...

More from Gleb Bahmutov