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
}
- Given the same input, will always return the same output.
- Produces no side effects.
- 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)
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
these slides: slides.com/bahmutov/mvc-frp
all links: glebbahmutov.com/blog/cycle-conf