Øredev 2017
C / C++ / C# / Java / CoffeeScript / JavaScript / Node / Angular / Vue / Cycle.js / functional
JavaScript ninja, image processing expert, software quality fanatic
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
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
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
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
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.
standard --verbose --fix 'src/**/*.js'
standard: Use JavaScript Standard Style
(https://standardjs.com)
index.js:13:3:
'constant' is constant. (no-const-assign)
const numbers = [3, 1, 7]
const constant = 2
for (let k = 0; k < numbers.length; k += 1) {
console.log(numbers[k] * constant)
}
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
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]'
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
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)
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)
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')
})
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
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
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')
})
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()
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
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
numbers.map(byConstant).forEach(log)
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
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)
}
const numbers = immutable([3, 1, 7])
multiplyBy(2, numbers)
.forEach(unary(console.log))
function multiplyBy (K, numbers) {
const byConstant = multiply(K)
return numbers.map(byConstant)
}
const numbers = immutable([3, 1, 7])
multiplyBy(2, numbers)
.forEach(unary(console.log))
function main () {
const numbers = immutable([3, 1, 7])
multiplyBy(2, numbers)
.forEach(unary(console.log))
}
main()
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()
Push side-effects away from the core
Pushing side-effects in ServiceWorkers
Friday, 14.20 Room: Homo Agitatus
const numbers = [3, 1, 7]
multiplyBy(2, numbers)
.forEach(unary(console.log))
// 3
// 1
// 7
numbers.push(100)
nothing happens
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
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
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)
}
}
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)
}
}
Creates stream "subscription"
Starts event flow
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)
In reactive program, we don't need to return a second function to push out side effects. Just return a cold Observable
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)
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
it('tests second number', done => {
main()
.skip(1)
.take(1)
.subscribe(x => console.assert(x === 2), null, done)
})
test second number
lazy-evaluate
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
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
Synchronize
everything
vs
Concurrent
events
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
A functional and reactive JavaScript framework for predictable code
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
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$
function main({DOM}) {
...
const vtree$ = count$.map(count =>
div([
button('.decrement', 'Decrement'),
button('.increment', 'Increment'),
p('Counter: ' + count)
])
)
return { DOM: vtree$ }
}
view
Cycle.run(main, {
DOM: CycleDOM.makeDOMDriver('#app')
})
See in action at glebbahmutov.com/draw-cycle/
start app from main
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
slides.com/bahmutov/fp-apps
mention @bahmutov
when it comes out ...
bahmutov.com
everyone