Gleb Bahmutov PRO
JavaScript ninja, image processing expert, software quality fanatic
Ø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
By Gleb Bahmutov
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
JavaScript ninja, image processing expert, software quality fanatic