Practical universal apps

in

functional JavaScript

Øredev 2017

Dr Gleb Bahmutov PhD

C / C++ / C# / Java / CoffeeScript / JavaScript / Node / Angular / Vue / Cycle.js / functional

JavaScript ninja, image processing expert, software quality fanatic

VP of Eng at

Testing, the way it should be

Code always grows

more complex

Company lifetime

Number of engineers

How do you write a complex application?

A: Write complex code

B: Assemble simple pieces

Complex code

  • Giant singletons

  • Code is hard to test

  • Code is hard to reuse

Let us write safe, elegant and powerful code

Talk Outline

  • Simple problem

  • Testability, side effects, purity

  • Imperative vs Declarative

  • Functional reactive console app

  • Functional reactive web app

Listen, glance at code

Get the most from this talk

Get the slides

Read the code

Example problem

var numbers = [3, 1, 7]
var constant = 2
// 6 2 14

given an array of numbers, multiply each number by a constant and print the result.

var numbers = [3, 1, 7]
var constant = 2
for (var k = 0; k < numbers.length; k += 1) {
  console.log(numbers[k] * constant)
}
// 6
// 2
// 14

Imperative code

var numbers = [3, 1, 7]
var constant = 2
for (var k = 0; k < numbers.length; k += 1) {
  console.log(numbers[k] * constant)
}
console.log('k =', k)
// 6
// 2
// 14
// k = 3

Always something unexpected 

var numbers = [3, 1, 7]
var constant = 2
for (let k = 0; k < numbers.length; k += 1) {
  console.log(numbers[k] * constant)
}
console.log('k =', k)
// console.log('k =', k)
//                    ^
// ReferenceError: k is not defined

ES6 "let" 👍

var numbers = [3, 1, 7]
var constant = 2
for (let k = 0; k < numbers.length; k += 1) {
  console.log(numbers[k] * constant)
  constant = 10
}
// 6
// 10
// 70

Source code should reflect what really happens

var numbers = [3, 1, 7]
const constant = 2
for (let k = 0; k < numbers.length; k += 1) {
  console.log(numbers[k] * constant)
  constant = 10
}
// 6
// index.js:13
//  constant = 10
//           ^
// TypeError: Assignment to constant variable.

ES6 "const" 👍

standard --verbose --fix 'src/**/*.js'

standard: Use JavaScript Standard Style 
    (https://standardjs.com)
  index.js:13:3: 
    'constant' is constant. (no-const-assign)

Static code linting 👍

const numbers = [3, 1, 7]
const constant = 2
for (let k = 0; k < numbers.length; k += 1) {
  console.log(numbers[k] * constant)
}

Let's make "numbers" constant too

const numbers = [3, 1, 7]
const constant = 2
numbers[0] = 100
for (let k = 0; k < numbers.length; k += 1) {
  console.log(numbers[k] * constant)
}
// 200
// 2
// 14

JavaScript "const" is a liar

const X = Y

X cannot point at anything else but Y

const immutable = require('seamless-immutable')
const numbers = immutable([3, 1, 7])
const constant = 2
numbers[0] = 100
// index.js:4
// numbers[0] = 100
//            ^
// TypeError: Cannot assign to read only 
//   property '0' of object '[object Array]'

JavaScript 3rd party libraries are awesome

const immutable = require('seamless-immutable')
const numbers = immutable([3, 1, 7])
const constant = 2
for (let k = 0; k < numbers.length; k += 1) {
  console.log(numbers[k] * constant)
}

Finally, imperative code that means what is says, and says what it means

Testing

it('prints numbers', () => {
  const sinon = require('sinon')
  sinon.spy(console, 'log')
  require('./index.js')
  console.assert(console.log.calledWith(6), 'printed 6')
  console.assert(console.log.calledWith(2), 'printed 2')
  console.assert(console.log.calledWith(14), 'printed 14')
})
mocha src/**/*spec.js

6
2
14
  ✓ prints numbers (154ms)

  1 passing (163ms)
it('prints numbers', () => {
  const sinon = require('sinon')
  sinon.spy(console, 'log')
  require('./index.js')
  console.assert(console.log.calledWith(6), 'printed 6')
  console.assert(console.log.calledWith(2), 'printed 2')
  console.assert(console.log.calledWith(14), 'printed 14')
})

it('does nothing', () => {
  console.assert(console.log.calledWith(6), 'printed 6')
  console.assert(console.log.calledWith(2), 'printed 2')
  console.assert(console.log.calledWith(14), 'printed 14')
})
mocha src/**/*spec.js

6
2
14
  ✓ prints numbers (178ms)
  ✓ does nothing

  2 passing (187ms)

Tests lie

it.only('does nothing', () => {
  console.assert(console.log.calledWith(6), 'printed 6')
  console.assert(console.log.calledWith(2), 'printed 2')
  console.assert(console.log.calledWith(14), 'printed 14')
})
mocha src/**/*spec.js

  1) does nothing

  0 passing (11ms)
  1 failing

  1) does nothing:
     TypeError: console.log.calledWith is not a function
      at Context.it.only (spec.js:12:30)

Tests are brittle

const sinon = require('sinon')
beforeEach(() => {
  sinon.spy(console, 'log')
})
afterEach(() => {
  console.log.restore()
})
it('prints numbers', () => {
  require('./index.js')
  console.assert(console.log.calledWith(6), 'printed 6')
  console.assert(console.log.calledWith(2), 'printed 2')
  console.assert(console.log.calledWith(14), 'printed 14')
})

Reset spies

require is a problem

require('./index.js')
  • Loads source from "index.js"
  • Evaluates the source
  • Puts result into "require.cache"
require('./index.js')
6
2
14
require('./index.js')

cache['./index.js'] =
// unchanged
cache = {}
require('./index.js')
6
2
14
require('./index.js')

cache['./index.js'] =
cache['./index.js'] =
cache = {}

Side effects

const immutable = require('seamless-immutable')
function multiplyAndPrint () {
  const numbers = immutable([3, 1, 7])
  const constant = 2
  for (let k = 0; k < numbers.length; k += 1) {
    console.log(numbers[k] * constant)
  }
}
module.exports = multiplyAndPrint

Defer execution

const multiplyThenPrint = require('./index')
it('prints numbers', () => {
  multiplyThenPrint()
  console.assert(console.log.calledWith(6), 'printed 6')
  console.assert(console.log.calledWith(2), 'printed 2')
  console.assert(console.log.calledWith(14), 'printed 14')
})
it('prints numbers again', () => {
  multiplyThenPrint()
  console.assert(console.log.calledWith(6), 'printed 6')
  console.assert(console.log.calledWith(2), 'printed 2')
  console.assert(console.log.calledWith(14), 'printed 14')
})
6
2
14
6
2
14

prints numbers

prints numbers

again

function multiplyAndPrint () {
  const numbers = immutable([3, 1, 7])
  const constant = 2
  for (let k = 0; k < numbers.length; k += 1) {
    console.log(numbers[k] * constant)
  }
}
module.exports = multiplyAndPrint

Side effect

Testing this code

Mocking this code

Mocking smell

multiply

console.log

function multiplyAndPrint (cb) {
  const numbers = immutable([3, 1, 7])
  const constant = 2
  for (let k = 0; k < numbers.length; k += 1) {
    cb(numbers[k] * constant)
  }
}
module.exports = multiplyAndPrint
module.exports = multiplyAndPrint
if (!module.parent) {
  // "app"
  multiplyAndPrint(console.log)
}
const sinon = require('sinon')
const multiplyThenPrint = require('./index')
it('produces numbers', () => {
  const cb = sinon.spy()
  multiplyThenPrint(cb)
  console.assert(cb.calledWith(6), 'produced 6')
  console.assert(cb.calledWith(2), 'produced 2')
  console.assert(cb.calledWith(14), 'produced 14')
})

Mocking ➡ Passing arguments

Improve Testability

function multiplyAndPrint (cb) {
  const numbers = immutable([3, 1, 7])
  const constant = 2
  for (let k = 0; k < numbers.length; k += 1) {
    cb(numbers[k] * constant)
  }
}
module.exports = multiplyAndPrint

multiplyAndPrint is testable, but still produces a side effect

const T = () => true
const I = (x) => x

pure functions

setTimeout(...)
new Promise(...)
window.location = ...

not pure

const HelloWorld = ({name}) => {
  return (
    <div>Hello {name}</div>
  )
}

pure function

$el.innerHTML = ...

not pure

const h = require('virtual-dom/h')
const render = ({name}) =>
  h('div', {}, [`Hello ${name}`])

pure function

function main (console) {
  return function inner () {
    console.log('Hello')
  }
}
const app = main(console)
app()

Note: a pure function can return impure function

pure

impure

impure

pure

defer execution!

require executes code ➡ export a function

log ➡ return a function that logs

Setup a function that will do side effect

But let someone else call it

Talk Outline

  • Simple problem

  • Testability, side effects, purity

  • Imperative vs Declarative

  • Functional reactive console app

  • Functional reactive web app

Imperative vs Declarative

function multiplyAndPrint (cb) {
  const numbers = immutable([3, 1, 7])
  const constant = 2
  for (let k = 0; k < numbers.length; k += 1) {
    cb(numbers[k] * constant)
  }
}
function multiplyAndPrint (cb) {
  const numbers = immutable([3, 1, 7])
  const constant = 2
  const byConstant = x => x * constant
  const log = x => cb(x)
  numbers.map(byConstant).forEach(log)
}

imperative

declarative

Elegant, simple,

easy to read code

numbers.map(byConstant).forEach(log)

Curried functions

const mul = x => y => x * y
const constant = 2
const byConstant = mul(constant)
// byConstant = y => constant * y
[1, 2, 3].map(byConstant)
// [2, 4, 6]
const {multiply, unary} = require('ramda')
function multiplyAndPrint (cb) {
  const numbers = immutable([3, 1, 7])
  const constant = 2
  const byConstant = multiply(constant)
  const log = unary(cb)
  numbers.map(byConstant).forEach(log)
}
module.exports = multiplyAndPrint

JavaScript 3rd party libraries are awesome

function multiplyAndPrint (K, numbers, cb) {
  const byConstant = multiply(K)
  const log = unary(cb)
  numbers.map(byConstant).forEach(log)
}
if (!module.parent) {
  const numbers = immutable([3, 1, 7])
  multiplyAndPrint(2, numbers, console.log)
}

Use Inputs

const numbers = immutable([3, 1, 7])
multiplyBy(2, numbers)
  .forEach(unary(console.log))

Finally, pure "multiplyBy"

function multiplyBy (K, numbers) {
  const byConstant = multiply(K)
  return numbers.map(byConstant)
}

impure

Inputs ➡ Output

const numbers = immutable([3, 1, 7])
multiplyBy(2, numbers)
  .forEach(unary(console.log))

Can we make this logic pure?

function main () {
  const numbers = immutable([3, 1, 7])
  multiplyBy(2, numbers)
    .forEach(unary(console.log))
}
main()

Can we make this logic pure?

function main (print) {
  const constant = 2
  const numbers = immutable([3, 1, 7])
  return function dirty () {
    multiplyBy(constant, numbers).forEach(print)
  }
}
const app = main(unary(console.log))
app()

Return function to do dirty work

impure

pure

Push side-effects away from the core

Pushing side-effects in ServiceWorkers

Gleb Bahmutov

The future belongs to you (and your JavaScript)

Friday, 14.20 Room: Homo Agitatus

Talk Outline

  • Simple problem

  • Testability, side effects, purity

  • Imperative vs Declarative

  • Functional reactive console app

  • Functional reactive web app

Going beyond arrays

const numbers = [3, 1, 7]
multiplyBy(2, numbers)
  .forEach(unary(console.log))
// 3
// 1
// 7
numbers.push(100)

nothing happens

Reactive streams

Arrays with superpowers

Reactive streams

const Rx = require('rxjs/Rx')
function main (print) {
  const constant = 2
  const numbers = Rx.Observable.of(3, 1, 7)
  return function dirty () {
    multiplyBy(constant, numbers).forEach(print)
  }
}
main(console.log)()
6
2
14

prints multiplied numbers

Reactive streams

const Rx = require('rxjs/Rx')
function main (print) {
  const constant = 2
  const numbers = Rx.Observable.interval(1000)
  return function dirty () {
    multiplyBy(constant, numbers).forEach(print)
  }
}
main(console.log)()
0
2
4
6
...

prints multiplied seconds

function main (print) {
  const constant = 2
  const numbers = Rx.Observable.of(3, 1, 7)
  return function dirty () {
    const seconds = Rx.Observable.interval(1000)
    const numberPerSecond = Rx.Observable.zip(
      numbers,
      seconds,
      (number, seconds) => number
    )
    multiplyBy(constant, numberPerSecond)
      .forEach(print)
  }
}
6
2
14

prints a multiplied number

every second

// numbers -3-1-7-|------------->
// seconds -0---1---2---3---4--->

// zip: (number, second) => number

// num/sec -3---1---7-|--------->

// map: x => 2 * x

// result  -6---2---14-|-->
6
2
14

Marble diagrams

prints a multiplied number

every second

function main (print) {
  const constant = 2
  const numbers = Rx.Observable.of(3, 1, 7)
  const seconds = Rx.Observable.interval(1000)
  const numberPerSecond = Rx.Observable.zip(
    numbers,
    seconds,
    (number, seconds) => number
  )
  return function dirty () {
    multiplyBy(constant, numberPerSecond)
      .forEach(print)
  }
}

impure

pure

Creates stream "subscription"

Starts event flow

function main (print) {
  const constant = 2
  const numbers = Rx.Observable.of(3, 1, 7)
  const seconds = Rx.Observable.interval(1000)
  const numberPerSecond = Rx.Observable.zip(
    numbers,
    seconds,
    (number, seconds) => number
  )
  return function dirty () {
    multiplyBy(constant, numberPerSecond)
      .subscribe(print)
  }
}

pure

Creates stream "subscription"

Starts event flow

impure

When you connect pipe system, it is all clean.

It only gets dirty after the first flush.

function main () {
  const constant = 2
  const numbers = Rx.Observable.of(3, 1, 7)
  const seconds = Rx.Observable.interval(1000)
  const numberPerSecond = Rx.Observable.zip(
    numbers,
    seconds,
    (number, seconds) => number
  )
  return multiplyBy(constant, numberPerSecond)
}
main().subscribe(console.log)

pure

impure

In reactive program, we don't need to return a second function to push out side effects. Just return a cold Observable

Defer execution

function main (print) {
  return function dirty () {
    print()
  }
}
const app = main(console.log)
app()
function main () {
  return Observable...
    .map(...)
    .filter(...)
    .zip(...)
}
const app = main()
app.subscribe(console.log)

pure

impure

pure

impure

it('multiplies numbers', done => {
  this.timeout(2500)
  const app = main()
  const numbers = []
  app.subscribe(x => numbers.push(x), null, () => {
    console.assert(equals(numbers, [6, 2, 14]))
    done()
  })
})

direct side effect into the test result

Testability

it('tests second number', done => {
  main()
    .skip(1)
    .take(1)
    .subscribe(x => console.assert(x === 2), null, done)
})

test second number

lazy-evaluate

Testability

Reactive Console App

const readline = require('readline')
readline.emitKeypressEvents(process.stdin)
if (process.stdin.isTTY) {
  process.stdin.setRawMode(true)
}
function getKeys () {
  const key$ = Rx.Observable.fromEvent(
    process.stdin, 'keypress')
  const enter$ = key$.filter(s => s === '\r')
  return key$.takeUntil(enter$)
}

stream of keyboard presses

Reactive Console App

const constant = 2
const key$ = getKeys()
const app = main(constant, key$.map(Number))
app.subscribe(console.log, null, () => {
  console.log('done with keys')
  process.exit(0)
})

multiplies user numbers

We pushed input and output streams outside of "main" function

Reactive Web App

User

main

server

Synchronize

everything

vs

Concurrent

events

Reactive Web App

const h = require('virtual-dom/h')
const constant = 2
const numbers$ = 
  Rx.Observable.fromEvent($input, 'input')
  .map(event => event.target.value)
const app = main(constant, numbers$)
  .map(x => h('div', {}, [x]))
app.subscribe(vdom => {
  patchDOM(vdom)
})

stream of values from DOM

stream of output virtual doms

Reactive: your code only reacts to events

You never command anyone.

no .setState, no db.update, no view.render()

Functional: functions are building blocks

no this, no new, no class

Cycle.js

A functional and reactive JavaScript framework for predictable code

Cycle.js

function main({DOM}) {
  const decrement$ = DOM
    .select('.decrement').events('click')
    .map(ev => -1)
  const increment$ = DOM
    .select('.increment').events('click')
    .map(ev => +1)
...

Decrement

-1

-1

-1

-1

Increment

1

1

1

intent

Cycle.js

function main({DOM}) {
  ...
  const action$ = 
    Observable.merge(decrement$, increment$)
  const count$ = 
    action$.startWith(0).scan((x,y) => x+y)

-1

-1

-1

-1

1

1

1

-1

-1

-2

-1

0

-1

0

0

model

action$

count$

Cycle.js

function main({DOM}) {
  ...
  const vtree$ = count$.map(count =>
    div([
      button('.decrement', 'Decrement'),
      button('.increment', 'Increment'),
      p('Counter: ' + count)
    ])
  )
  return { DOM: vtree$ }
}

view

Cycle.js

Cycle.run(main, { 
  DOM: CycleDOM.makeDOMDriver('#app') 
})

See in action at glebbahmutov.com/draw-cycle/

start app from main

Pass Observables everywhere

Virtual DOM

HTTP requests / responses

Route changes

State updates

Parent / child components

Cycle.js on the server

server.use(function (req, res) {
  run(main, {
    DOM: makeHTMLDriver(html => res.send(html))
  })
})
server.listen(process.env.PORT)

Isolated rendering ➡ simple to render into HTML and send to the client

Universal

Why Cycle.js is Hard

You must plan your app's entire event flow at once

Conclusions

Move side effects away from logic

Defer execution

  • return impure function for someone else to call
  • return Observable for someone else to start

Conclusions

Build reactive functional apps

  • Use Observables as arguments / return value
  • Process events in the pipeline using small simple callbacks

Check out Cycle.js

Thank You  👏 Øredev

slides.com/bahmutov/fp-apps

mention @bahmutov

when it comes out ...

bahmutov.com

everyone

Practical Universal Apps in Functional JavaScript

By Gleb Bahmutov

Practical Universal Apps in Functional JavaScript

Writing solid code is hard. Writing portable code that can run on the server and in the browser is harder. Writing readable code that is easy to modify and extend might be impossible. Let us try achieve all three goals by writing JavaScript in functional style. JS is no Haskell, but can mimic a traditional functional language pretty well, as I will show in this presentation. Plus everything in the world can run JavaScript, so FP in JS knowledge applies very widely. Presented at Øredev 2017 video at https://vimeo.com/242030169

  • 794
Loading comments...

More from Gleb Bahmutov