Gleb Bahmutov PRO
JavaScript ninja, image processing expert, software quality fanatic
Dr. Gleb Bahmutov PhD
Kensho Boston / NYC
Language where equality == looks like this?
Language where < looks like this?
JavaScript - not just Netscape
source: StackOverflow
source: StackOverflow
clojure 19 haskell 21 ocaml 11 scala 105 elm 0 f# 16 erlang 21 idris 0 ----------- 193
source: http://careers.stackoverflow.com/jobs?searchTerm=<language> in February 2015
similar results from: http://www.sitepoint.com/whats-best-programming-language-learn-2015/
similar results when searching by company: http://careers.stackoverflow.com/companies?searchTerm=<language>
source: really? you don't know who these are?
Changing how JS developers program will have a huge impact on the software quality
in the world
Kensho financial analytics
2 major parts
Everyone has their own path to JS
! junior vs senior
In theory
anyone
functional reactive programmer
In practice
anyone
functional reactive programmer
...
...
...
...
...
...
...
Problem: given an array of numbers, multiply each number by a constant and print the result.
var numbers = [3, 1, 7];
var constant = 2;
// 6 2 14
var numbers = [3, 1, 7];
var constant = 2;
var k = 0;
for(k = 0; k < numbers.length; k += 1) {
console.log(numbers[k] * constant);
}
// 6 2 14
var numbers = [3, 1, 7];
var constant = 2;
var k = 0;
for(k = 0; k < numbers.length; k += 1) {
console.log(numbers[k] * constant);
}
// 6 2 14
Start splitting code into procedures
var numbers = [3, 1, 7];
var constant = 2;
function mul(a, b) {
return a * b;
}
function processNumber(n, constant) {
console.log(mul(n, constant));
}
for(k = 0; k < numbers.length; k += 1) {
processNumber(numbers[k], constant);
}
// 6 2 14
Boring code does not surprise you
// for loops WILL surprise you
for(k = 0; k < numbers.length; k += 1) {
processNumber(numbers[k], constant);
}
Boring code does not surprise you
// using "this" WILL surprise you
numbers.forEach(function process(n) {
return this.mul(n, 2);
});
Boring code does not surprise you
// using non-local variables WILL surprise you
constant = 2;
...
numbers.forEach(function process(n) {
return mul(n, constant);
});
function mul(a, b) {
return a * b;
}
function print(n) {
console.log(n);
}
function mul(a, b) {
return a * b;
}
var mulBy2 = mul.bind(null, 2);
var mul2by3 = mul.bind(null, 2, 3);
var print = console.log.bind(console, 'RESULT');
print(mulBy2(10));
// RESULT 20
print(mul2by3(), mul2by3());
// RESULT 6 6
Produces new array
Passes each number through
a callback function
Returns nothing
Side effects
Produces new array
Passes only items for which the callback returns truthy value
Object-Oriented EcmaScript5
List -> single value
Built-in .forEach
var numbers = [3, 1, 7];
var constant = 2;
// use built-in Array.prototype.forEach
numbers.forEach(function (n) {
processNumber(n, constant);
});
// 6 2 14
Object-oriented way
var numbers = [3, 1, 7];
var constant = 2;
function mul(a, b) {
return a * b;
}
function print(n) {
console.log(n);
}
numbers.map(function (n) {
return mul(n, constant);
}).forEach(print);
// 6 2 14
// OO (built-in)
Array.prototype.something(x, y)
// functional (user space)
Library.something(array, x, y)
Library.another(array, k);
var _ = require('lodash');
var byConstant = _.partial(mul, constant);
_(numbers)
.map(byConstant)
.forEach(print);
// 6 2 14
var _ = require('lodash');
_.forEach(
_.map(numbers, byConstant),
print);
// 6 2 14
// OO (built-in)
Array.prototype.something(x, y)
// lodash / underscore
Library.something(array, x, y)
// Ramda
Library.something(x, y, array)
var R = require('ramda');
var mapByConstant = R.map(byConstant);
var printEach = R.forEach(print);
var algorithm = R.compose(printEach, mapByConstant);
algorithm(numbers);
// 6 2 14
1. compose algorithm
2. apply to data
3. profit!!!
_({ John: 3, Jim: 10 })
.map(byConstant)
.forEach(print);
// 6 20
Treat objects same as collections
"Smart" objects
Pass data around
"Dumb" objects
"Smart" pipeline
Pass code (functions) around
new NumberMultiplier()
.add(3, 1, 7)
.multiply(2)
.print();
_([3, 1, 7])
.map(_.partial(mul, 2))
.forEach(print);
new NumberMultiplier()
.add(3, 1, 7)
.multiply(2)
.print();
_([3, 1, 7])
.map(_.partial(mul, 2))
.forEach(print);
It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures."
- Alan J. Perlis
composable algorithmic transformations
Tell return values Bye-bye
// imperative code
var sum = add(2, 3);
// can use sum right away!
var multiplied = [1, 2, 3].map(mul);
// can use multiplied array right away!
var _ = require('lodash');
var byConstant = _.partial(mul, constant);
_(numbers)
.map(byConstant)
.forEach(print);
// we have not returned any values!
Tell middle functions Bye-bye
var byConstant = _.partial(mul, constant);
_(numbers)
.map(function (x) {
return byConstant(x);
})
.forEach(function (x) {
print(x);
});
_(numbers)
.map(byConstant)
.forEach(print);
// no middle functions
Tell side-effects Bye-bye
var byConstant = _.partial(mul, constant);
_(numbers)
.map(byConstant)
.forEach(function (x, k, array) {
array[1] = 10000;
print(x);
});
with immutable data structures
Tell side-effects Bye-bye
'use strict';
var immutable = require('seamless-immutable');
var byConstant = _.partial(mul, constant);
immutable(numbers)
.map(byConstant)
.forEach(function (x, k, array) {
array[1] = 10000; // throws an Error
print(x);
});
with immutable data structures
Efficient immutable JS library mori
var _ = require('lodash');
// lodash v3.x.x
var byConstant = _.partial(mul, constant);
// avoid intermediate arrays
_(numbers)
.map(byConstant)
.take(2)
.forEach(print);
.value();
// 6
// 2
var lazy = require('lazy.js');
lazy(numbers)
.async(1000) // 1000ms = 1 sec
.map(byConstant)
.each(print);
// 2
// 1 second later
// 6
// 1 second later
// 14
var Q = require('q');
Q(...)
.then(function onSuccess(result) { ... },
function onFailure(error) { ... })
.then(...);
A single async action with a single result or error
var Q = require('q');
function asyncMul(a, b) {
return Q(a * b).delay(1000);
}
var byConstantAsync = _.partial(asyncMul, constant);
var promiseToMulNumber = function (n) {
return byConstantAsync(n);
};
Q.all(numbers.map(promiseToMulNumber))
.then(print)
.done();
// sleeps 1 second
// then prints [6, 2, 14]
function asyncMul(a, b) {
return Q(mul(a, b))
}
var sleep1 = _.partial(Q.delay, 1000);
Q()
.then(sleep1)
.then(_.partial(byConstantAsync, numbers[0]))
.then(print)
.then(sleep1)
.then(_.partial(byConstantAsync, numbers[1]))
.then(print)
.then(sleep1)
.then(_.partial(byConstantAsync, numbers[2]))
.then(print)
.done();
// ... 6 ... 2 ... 14
function asyncMul(a, b) {
return Q(mul(a, b))
}
var sleep1 = _.partial(Q.delay, 1000);
var mulAndPrint = function (n) {
return function () {
return sleep1()
.then(_.partial(byConstantAsync, n))
.then(print);
};
};
numbers.map(mulAndPrint)
.reduce(Q.when, Q())
.done();
// ... 6 ... 2 ... 14
function asyncMul(a, b) {
return Q(mul(a, b))
}
var sleep1 = _.partial(Q.delay, 1000);
var mulAndPrint = function (n) {
return function () {
return sleep1()
.then(_.partial(byConstantAsync, n))
.then(print);
};
};
numbers.map(mulAndPrint)
.reduce(Q.when, Q())
.done();
// ... 6 ... 2 ... 14
Promises handle single event
Extra complexity trying to handle sequence of events
function asyncMul(a, b) {
return Q(mul(a, b))
}
var sleep1 = _.partial(Q.delay, 1000);
var mulAndPrint = function (n) {
return function () {
return sleep1()
.then(_.partial(byConstantAsync, n))
.then(print);
};
};
numbers.map(mulAndPrint)
.reduce(Q.when, Q())
.done();
// ... 6 ... 2 ... 14
// action
var events = require('events');
var numberEmitter = new events.EventEmitter();
numberEmitter.on('number',
_.compose(print, byConstant)
);
// async event source
lazy(numbers)
.async(1000)
.each(
_.bind(numberEmitter.emit, numberEmitter, 'number')
);
// prints 6, 2 14 with 1 second intervals
// action
var events = require('events');
var numberEmitter = new events.EventEmitter();
numberEmitter.on('number',
_.compose(print, byConstant)
);
// async event source
lazy(numbers)
.async(1000)
.each(
_.bind(numberEmitter.emit, numberEmitter, 'number')
);
// prints 6, 2 14 with 1 second intervals
Everything but marked lines is boilterplate
function source(list) {
var eventEmitter = new events.EventEmitter();
lazy(list)
.async(1000)
.each(
_.bind(eventEmitter.emit, eventEmitter, 'step')
);
return {
on: function (cb) {
eventEmitter.on('step', cb);
}
};
}
source(numbers)
.on(_.compose(print, byConstant));
// prints 6, 2 14 with 1 second intervals
Decouple action
function source(generate) {
var eventEmitter = new events.EventEmitter();
generate(
_.bind(eventEmitter.emit, eventEmitter, 'step')
);
return {
on: function (cb) {
eventEmitter.on('step', cb);
}
};
}
var generator = lazy(list).async(1000);
source(_.bind(generator.each, generator))
.on(_.compose(print, byConstant));
// prints 6, 2 14 with 1 second intervals
Decouple generator
var generator = lazy(list).async(1000);
source(_.bind(generator.each, generator))
.on(_.compose(print, byConstant));
Just the user code
Widely used in browser code
$(el).on('click', action)
window.onmessage = action
socket.on('message', action)
We just extended the same approach to any event generator (like number sequence)
Are we overcomplicating things?
// sync event generation
// sync action
action(eventGenerator);
// any event generation
// async action
source(eventGenerator)
.on(action);
source(eventGenerator)
.on(action);
[3, 1, 7]
.map(mulBy2)
.forEach(print);
source of events
.map(an async callback)
.forEach(another async callback);
Chained emitters
var stepEmitter = {
map: function (cb) {
var emitter = new events.EventEmitter();
this.on('step', function (value) {
var mappedValue = cb(value);
emitter.emit('step', mappedValue);
});
return _.extend(emitter, stepEmitter);
},
forEach: function (cb) {
var emitter = new events.EventEmitter();
this.on('step', function (value) {
cb(value);
emitter.emit('step', value);
});
return _.extend(emitter, stepEmitter);
}
};
// returns stepEmitter = event emitter + stepEmitter methods
function source(list) {
var eventEmitter = new events.EventEmitter();
lazy(list)
.async(1000)
.each(_.bind(eventEmitter.emit, eventEmitter, 'step'));
return _.extend(eventEmitter, stepEmitter);
}
// each step returns s new stepEmitter
source(numbers)
.map(byConstant)
.forEach(print);
// prints 6, 2 14 with 1 second intervals
var stepEmitter = {
// same map and forEach methods as above
// returns a new step emitter that accumulates N items
// emits the entire array with N items as single argument
buffer: function (n) {
var received = [];
var emitter = new events.EventEmitter();
this.on('step', function (value) {
received.push(value);
if (received.length === n) {
emitter.emit('step', received);
received = [];
}
});
return _.extend(emitter, stepEmitter);
}
};
source(numbers)
.map(byConstant)
.buffer(3)
.forEach(print);
// sleeps 3 seconds then prints [6, 2, 14]
source(numbers)
.map(byConstant) // data transform
.buffer(3) // sequence control
.forEach(print); // data transform
We implemented 2 types
of operations
on a sequence of events
.map, .filter
// vs
.buffer
Take a look at Highland.js
Each high level "event" can be a sequence of low-level built-in "events"
Use a reactive FP library
var Rx = require('rx');
var timeEvents = Rx.Observable
.interval(1000)
.timeInterval(); // stream 1
var numberEvents = Rx.Observable
.fromArray(numbers); // stream 2
Rx.Observable.zip(timeEvents, numberEvents,
function pickValue(t, n) { return n; })
.map(byConstant)
.subscribe(print); // stream 3
// prints 6 2 14 with 1 second intervals
Decoupled time from numbers
var timeEvents = Rx.Observable
.interval(1000)
.timeInterval();
var numberEvents = Rx.Observable
.fromArray(numbers);
Rx.Observable.zip(timeEvents, numberEvents,
function pickValue(t, n) {
return n;
});
User events
var clickEvents = Rx.Observable
.fromEvent(document.querySelector('#btn'), 'click')
var numberEvents = Rx.Observable
.fromArray(numbers);
Rx.Observable.zip(clickEvents, numberEvents,
function pickValue(whatever, n) {
return n;
});
function searchWikipedia (term) {
return $.ajax({
url: 'http://en.wikipedia.org/w/api.php',
dataType: 'jsonp',
data: {
action: 'opensearch',
format: 'json',
search: term
}
}).promise();
}
var keyups = Rx.Observable.fromEvent($('#input'), 'keyup')
.map(function (e) {
return e.target.value;
})
.filter(function (text) {
return text.length > 2;
});
keyups.throttle(500)
.distinctUntilChanged()
.flatMapLatest(searchWikipedia)
.subscribe(function (data) {
// display results
});
object-oriented ** 2 /10
functional ******** 8 /10
immutable data ** 2 /10
lazy evaluation * 1 /10
promises ********* 9 /10
event emitters ******* 7 /10
reactive streams * 1 /10
Dr. Gleb Bahmutov PhD
By Gleb Bahmutov
How do we convince / train developers used to imperative programming style to switch to functional or even reactive programming? I show the same example implemented in multiple styles to highlight the advantages one would get by going from imperative to object-oriented, functional, lazy evaluation, promise-based, event emitters, and finally reactive approach. The short examples are in JavaScript - the most widely used functional language today.
JavaScript ninja, image processing expert, software quality fanatic