Download - Javascript Combinators (2016)
-
2016 Reginald Braithwaite. Some rights reserved. 1
http://creativecommons.org/licenses/by-sa/4.0/
-
JavaScript Combinators (2016)
2016 Reginald Braithwaite. Some rights reserved. 2
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 3
http://creativecommons.org/licenses/by-sa/4.0/
-
we'll talk about
Using combinators for decomposi2on and composi2on
2016 Reginald Braithwaite. Some rights reserved. 4
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 5
http://creativecommons.org/licenses/by-sa/4.0/
-
and we'll think about
Making responsibili/es and rela/onships explicit
2016 Reginald Braithwaite. Some rights reserved. 6
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 7
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 8
http://creativecommons.org/licenses/by-sa/4.0/
-
Decomposi)on
2016 Reginald Braithwaite. Some rights reserved. 9
http://creativecommons.org/licenses/by-sa/4.0/
-
We decompose en%%es to make discreet responsibili%es explicit
2016 Reginald Braithwaite. Some rights reserved. 10
http://creativecommons.org/licenses/by-sa/4.0/
-
a monolith
Parse.User.logIn("user", "pass", { success: function (user) { query.find({ success: function (users) { users[0].save({ key: value }, { success: function (user) { currentUser = user; } }); } }); }});
2016 Reginald Braithwaite. Some rights reserved. 11
http://creativecommons.org/licenses/by-sa/4.0/
-
decomposi)on by extrac)ng func)ons
let assignCurrentUser = (user) => { currentUser = user; };
let saveFirstUser = (users) => users[0].save({ key: value }, { success: assignCurrentUser });
let logUserIn = (user) => query.find({ success: saveFirstUser });
Parse.User.logIn("user", "pass", { success: logUserIn});
2016 Reginald Braithwaite. Some rights reserved. 12
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 13
http://creativecommons.org/licenses/by-sa/4.0/
-
Composi'on
2016 Reginald Braithwaite. Some rights reserved. 14
http://creativecommons.org/licenses/by-sa/4.0/
-
We compose en%%es to make the rela%onships between them explicit
2016 Reginald Braithwaite. Some rights reserved. 15
http://creativecommons.org/licenses/by-sa/4.0/
-
promises explicitly compose asynchronous func3ons
let findUser = (user) => query.find();
let saveFirstUser = (user) => users[0].save({ key: value });
let assignCurrentUser = (user) => { currentUser = user; };
Parse.User.logIn("user", "pass") .then(findUser) .then(saveFirstUser) .then(assignCurrentUser);
2016 Reginald Braithwaite. Some rights reserved. 16
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 17
http://creativecommons.org/licenses/by-sa/4.0/
-
Decomposi)on is about en))es
2016 Reginald Braithwaite. Some rights reserved. 18
http://creativecommons.org/licenses/by-sa/4.0/
-
Composi'on is about rela'onships
2016 Reginald Braithwaite. Some rights reserved. 19
http://creativecommons.org/licenses/by-sa/4.0/
-
Back to decomposi.on
2016 Reginald Braithwaite. Some rights reserved. 20
http://creativecommons.org/licenses/by-sa/4.0/
-
extrac'ng named func'ons
The most obvious form of decomposi2on
2016 Reginald Braithwaite. Some rights reserved. 21
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 22
http://creativecommons.org/licenses/by-sa/4.0/
-
Extrac'ng func'ons works from the inside-out
2016 Reginald Braithwaite. Some rights reserved. 23
http://creativecommons.org/licenses/by-sa/4.0/
-
extrac'ng named func'ons
Decomposi)on of Implementa)on
2016 Reginald Braithwaite. Some rights reserved. 24
http://creativecommons.org/licenses/by-sa/4.0/
-
Let's look at something else
2016 Reginald Braithwaite. Some rights reserved. 25
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 26
http://creativecommons.org/licenses/by-sa/4.0/
-
pluck: "A convenient version of what is perhaps the most common use-case for map,
extrac8ng a list of property values."
2016 Reginald Braithwaite. Some rights reserved. 27
http://creativecommons.org/licenses/by-sa/4.0/
-
let pluck = (collection, property) => collection.map( (obj) => obj[property] );
var deStijl = [ {name: 'Theo van Doesburg', occupation: 'theorist'}, {name: 'Piet Mondriaan', occupation: 'painter'}, {name: 'Gerrit Rietveld', occupation: 'architect'}];
pluck(deStijl, 'name') //=> ["Theo van Doesburg", "Piet Mondriaan", "Gerrit Rietveld"]
2016 Reginald Braithwaite. Some rights reserved. 28
http://creativecommons.org/licenses/by-sa/4.0/
-
func%ons have interfaces
pluck's interface has two parts: The collec0on, and the property
2016 Reginald Braithwaite. Some rights reserved. 29
http://creativecommons.org/licenses/by-sa/4.0/
-
manually decomposing pluck's interface
let pluckFrom = (collection) => (property) => pluck(collection, property);
var deStijl = [ {name: 'Theo van Doesburg', occupation: 'theorist'}, {name: 'Piet Mondriaan', occupation: 'painter'}, {name: 'Gerrit Rietveld', occupation: 'architect'}];
pluckFrom(deStijl)('name') //=> ["Theo van Doesburg", "Piet Mondriaan", "Gerrit Rietveld"]
2016 Reginald Braithwaite. Some rights reserved. 30
http://creativecommons.org/licenses/by-sa/4.0/
-
manually decomposing pluck's interface
let pluckWith = (property) => (collection) => pluck(collection, property);
var deStijl = [ {name: 'Theo van Doesburg', occupation: 'theorist'}, {name: 'Piet Mondriaan', occupation: 'painter'}, {name: 'Gerrit Rietveld', occupation: 'architect'}];
pluckFrom(deStijl)('name') //=> ["Theo van Doesburg", "Piet Mondriaan", "Gerrit Rietveld"]
2016 Reginald Braithwaite. Some rights reserved. 31
http://creativecommons.org/licenses/by-sa/4.0/
-
pluckFrom and pluckWith par'ally apply pluck
2016 Reginald Braithwaite. Some rights reserved. 32
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 33
http://creativecommons.org/licenses/by-sa/4.0/
-
Par$al applica$on decomposes func$ons from the outside-in
2016 Reginald Braithwaite. Some rights reserved. 34
http://creativecommons.org/licenses/by-sa/4.0/
-
par$al applica$on
Decomposi)on of Interface
2016 Reginald Braithwaite. Some rights reserved. 35
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 36
http://creativecommons.org/licenses/by-sa/4.0/
-
Decorators
2016 Reginald Braithwaite. Some rights reserved. 37
http://creativecommons.org/licenses/by-sa/4.0/
-
A decorator is a higher-order func1on that takes a func1on, and returns another func1on that adds
to or modifies its argument's behaviour.
2016 Reginald Braithwaite. Some rights reserved. 38
http://creativecommons.org/licenses/by-sa/4.0/
-
par$al applica$on decomposes a func$on from the outside-in
let pluck = (collection, property) => collection.map( (obj) => obj[property] );
// decomposes into:
let pluckFrom = (collection) => (property) => pluck(collection, property);
2016 Reginald Braithwaite. Some rights reserved. 39
http://creativecommons.org/licenses/by-sa/4.0/
-
extract closed-over binding
let pluckFrom = (collection) => (property) => pluck(collection, property);
// becomes:
let leftApply = (fn, a) => (b) => fn(a, b);
let pluckFrom = (collection) => leftApply(pluck, collection);
2016 Reginald Braithwaite. Some rights reserved. 40
http://creativecommons.org/licenses/by-sa/4.0/
-
extract closed-over binding
Closed over bindings can be extracted, just like parameters
2016 Reginald Braithwaite. Some rights reserved. 41
http://creativecommons.org/licenses/by-sa/4.0/
-
extract closed-over binding
Decomposing pluckFrom into leftApply gives us a decorator
2016 Reginald Braithwaite. Some rights reserved. 42
http://creativecommons.org/licenses/by-sa/4.0/
-
leftApply is a decorator that decomposes a func1on's interface
2016 Reginald Braithwaite. Some rights reserved. 43
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 44
http://creativecommons.org/licenses/by-sa/4.0/
-
hmmmm
What does leftApply(leftApply,
leftApply) do?
2016 Reginald Braithwaite. Some rights reserved. 45
http://creativecommons.org/licenses/by-sa/4.0/
-
back to par*al applica*on
let rightApply = (fn, b) => (a) => fn(a, b);
let pluckWith = (property) => rightApply(pluck, property);
2016 Reginald Braithwaite. Some rights reserved. 46
http://creativecommons.org/licenses/by-sa/4.0/
-
more decomposi+on with par+al applica+on
let get = (object, property) => object[property];
get({name: 'Gerrit Rietveld'}, 'name') //=> Gerrit Rietveld
let getWith = (property) => rightApply(get, property);
let nameOf = getWith('name');
nameOf({name: 'Gerrit Rietveld'}) //=> Gerrit Rietveld
2016 Reginald Braithwaite. Some rights reserved. 47
http://creativecommons.org/licenses/by-sa/4.0/
-
more decomposi+on with par+al applica+on
let map = (collection, fn) => collection.map(fn);
let mapWith = (fn) => rightApply(map, property);
2016 Reginald Braithwaite. Some rights reserved. 48
http://creativecommons.org/licenses/by-sa/4.0/
-
Back to composi,on
2016 Reginald Braithwaite. Some rights reserved. 49
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 50
http://creativecommons.org/licenses/by-sa/4.0/
-
Simple Composi+on
2016 Reginald Braithwaite. Some rights reserved. 51
http://creativecommons.org/licenses/by-sa/4.0/
-
let compose = (a, b) =>(c) => a(b(c));
2016 Reginald Braithwaite. Some rights reserved. 52
http://creativecommons.org/licenses/by-sa/4.0/
-
compose in ac&on
var deStijl = [ {name: 'Theo van Doesburg', occupation: 'theorist'}, {name: 'Piet Mondriaan', occupation: 'painter'}, {name: 'Gerrit Rietveld', occupation: 'architect'}];
let pluckWith = compose(mapWith, getWith);
let namesOf = pluckWith('name');
namesOf(deStijl) //=> ["Theo van Doesburg","Piet Mondriaan","Gerrit Rietveld"]
2016 Reginald Braithwaite. Some rights reserved. 53
http://creativecommons.org/licenses/by-sa/4.0/
-
pluckWith = compose(mapWith, getWith);compose wires the output of one func/on into the input of another
2016 Reginald Braithwaite. Some rights reserved. 54
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 55
http://creativecommons.org/licenses/by-sa/4.0/
-
reminder:
We compose en**es to make the rela*onships between them explicit
2016 Reginald Braithwaite. Some rights reserved. 56
http://creativecommons.org/licenses/by-sa/4.0/
-
More Composi+on
2016 Reginald Braithwaite. Some rights reserved. 57
http://creativecommons.org/licenses/by-sa/4.0/
-
let mix = (...ingredients) => console.log('mixing', ...ingredients);
let bake = () => console.log('baking');
let cool = () => console.log('cooling');
let makeBread = (...ingredients) => { mix(...ingredients); bake(); cool();}
2016 Reginald Braithwaite. Some rights reserved. 58
http://creativecommons.org/licenses/by-sa/4.0/
-
composi'on with before
let before = (fn, decoration) => (...args) => { decoration(...args); return fn(...args); };
let bakeBread = before(bake, mix);
let makeBread = (...ingredients) => { bakeBread(); cool();}
2016 Reginald Braithwaite. Some rights reserved. 59
http://creativecommons.org/licenses/by-sa/4.0/
-
before makes the )me rela)onship between two func)ons explicit
2016 Reginald Braithwaite. Some rights reserved. 60
http://creativecommons.org/licenses/by-sa/4.0/
-
composi'on with after
let after = (fn, decoration) => (...args) => { let returnValue = fn(...args); decoration(...args); return returnValue; };
let bakeBread = before(bake, mix);
let makeBread = after(bakeBread, cool);
2016 Reginald Braithwaite. Some rights reserved. 61
http://creativecommons.org/licenses/by-sa/4.0/
-
after also makes the +me rela+onship between two func+ons
explicit
2016 Reginald Braithwaite. Some rights reserved. 62
http://creativecommons.org/licenses/by-sa/4.0/
-
decomposing before
let beforeWith = (decoration) => rightApply(before, decoration);
let mixBefore = beforeWith(mix);
let bakeBread = mixBefore(bake);
2016 Reginald Braithwaite. Some rights reserved. 63
http://creativecommons.org/licenses/by-sa/4.0/
-
decomposing after
let afterWith = (decoration) => rightApply(after, decoration);
let coolAfter = afterWith(after);
let makeBread = coolAfter(bakeBread);
2016 Reginald Braithwaite. Some rights reserved. 64
http://creativecommons.org/licenses/by-sa/4.0/
-
beforeWith and afterWith are combinators that turn func1ons into decorators that compose behaviour
2016 Reginald Braithwaite. Some rights reserved. 65
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 66
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 67
http://creativecommons.org/licenses/by-sa/4.0/
-
JavaScript invoca-ons are coloured
2016 Reginald Braithwaite. Some rights reserved. 68
http://creativecommons.org/licenses/by-sa/4.0/
-
coloured decorators
let before = (fn, decoration) => function (...args) { decoration.apply(this, args); return fn.apply(this, args); };
let after = (fn, decoration) => function (...args) { let returnValue = fn.apply(this, args); decoration.apply(this, args); return returnValue; };
2016 Reginald Braithwaite. Some rights reserved. 69
http://creativecommons.org/licenses/by-sa/4.0/
-
Why coloured decorators ma0er
2016 Reginald Braithwaite. Some rights reserved. 70
http://creativecommons.org/licenses/by-sa/4.0/
-
bread, revisited
class Bread { constructor (...ingredients) { this.ingredients = ingredients; }
mix () { console.log('mixing', ...this.ingredients) };
bake () { console.log('baking'); }
cool () { console.log('cooling'); }
make () { this.mix(); this.bake(); this.cool(); }}
2016 Reginald Braithwaite. Some rights reserved. 71
http://creativecommons.org/licenses/by-sa/4.0/
-
bread, revisited
class Bread {
// ...
make () { this.mix(); this.bake(); this.cool(); }}
2016 Reginald Braithwaite. Some rights reserved. 72
http://creativecommons.org/licenses/by-sa/4.0/
-
Classes can be decorated too
2016 Reginald Braithwaite. Some rights reserved. 73
http://creativecommons.org/licenses/by-sa/4.0/
-
beforeAll
const beforeAll = (behaviour, ...methodNames) => (clazz) => { for (let methodName of methodNames) { const method = clazz.prototype[methodName];
Object.defineProperty(clazz.prototype, property, { value: function (...args) { behaviour.apply(this, args); return method.apply(this, args); }, writable: true }); } return clazz; };
2016 Reginald Braithwaite. Some rights reserved. 74
http://creativecommons.org/licenses/by-sa/4.0/
-
afterAll
const afterAll = (behaviour, ...methodNames) => (clazz) => { for (let methodName of methodNames) { const method = clazz.prototype[methodName];
Object.defineProperty(clazz.prototype, property, { value: function (...args) { const returnValue = method.apply(this, args);
behaviour.apply(this, args); return returnValue; }, writable: true }); } return clazz; };
2016 Reginald Braithwaite. Some rights reserved. 75
http://creativecommons.org/licenses/by-sa/4.0/
-
be#er bread
let invoke = (methodName) => function (...args) { return this[methodName](...args);}
let Bread = beforeAll(invoke('mix'), 'make')( afterAll(invoke('cool'), 'make')(class { // ...
make () { this.bake(); } }));
2016 Reginald Braithwaite. Some rights reserved. 76
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 77
http://creativecommons.org/licenses/by-sa/4.0/
-
Looking Forward
2016 Reginald Braithwaite. Some rights reserved. 78
http://creativecommons.org/licenses/by-sa/4.0/
-
ES.who-knows-when
2016 Reginald Braithwaite. Some rights reserved. 79
http://creativecommons.org/licenses/by-sa/4.0/
-
be#er bread with class decorator sugar
let invoke = (methodName) => function (...args) { return this[methodName](...args);}
@beforeAll(invoke('mix'), 'make')@afterAll(invoke('cool'), 'make')class Bread {
// ...
make () { this.bake(); }}
2016 Reginald Braithwaite. Some rights reserved. 80
http://creativecommons.org/licenses/by-sa/4.0/
-
method decorators
let methodDecorator = (decorator) => function (target, name, descriptor) { descriptor.value = decorator(descriptor.value); }
let invokeBefore = (methodName) => methodDecorator( (methodBody) => before(methodBody, invoke(methodName)) );
let invokeAfter = (methodName) => methodDecorator( (methodBody) => after(methodBody, invoke(methodName)) );
2016 Reginald Braithwaite. Some rights reserved. 81
http://creativecommons.org/licenses/by-sa/4.0/
-
be#er bread
class Bread {
// ...
@invokeBefore('mix') @invokeAfter('cool') make () { this.bake(); }}
2016 Reginald Braithwaite. Some rights reserved. 82
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 83
http://creativecommons.org/licenses/by-sa/4.0/
-
What have we seen so far?
2016 Reginald Braithwaite. Some rights reserved. 84
http://creativecommons.org/licenses/by-sa/4.0/
-
What have we seen so far? Extract func,on
Promise interface
Par,al applica,on
Extract closed-over binding
(more!)
2016 Reginald Braithwaite. Some rights reserved. 85
http://creativecommons.org/licenses/by-sa/4.0/
-
What have we seen so far? Simple composi,on
Composi,on decorators
Class decorators
Method decorators
2016 Reginald Braithwaite. Some rights reserved. 86
http://creativecommons.org/licenses/by-sa/4.0/
-
Don't worry about the details!
2016 Reginald Braithwaite. Some rights reserved. 87
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 88
http://creativecommons.org/licenses/by-sa/4.0/
-
it's all the same idea
Decomposi)on makes responsibili)es explicit
2016 Reginald Braithwaite. Some rights reserved. 89
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 90
http://creativecommons.org/licenses/by-sa/4.0/
-
and it's all the same idea
Composi'on makes rela'onships explicit
2016 Reginald Braithwaite. Some rights reserved. 91
http://creativecommons.org/licenses/by-sa/4.0/
-
These ideas ma*er
2016 Reginald Braithwaite. Some rights reserved. 92
http://creativecommons.org/licenses/by-sa/4.0/
-
There are only two hard problems in Computer Science: Cache
invalida9on, and naming things.
2016 Reginald Braithwaite. Some rights reserved. 93
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 94
http://creativecommons.org/licenses/by-sa/4.0/
-
Naming en))es is hard because you have to figure out which en))es
need to be named
2016 Reginald Braithwaite. Some rights reserved. 95
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 96
http://creativecommons.org/licenses/by-sa/4.0/
-
Naming rela+onships is hard because you have to figure out which rela+onships need to be
named
2016 Reginald Braithwaite. Some rights reserved. 97
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 98
http://creativecommons.org/licenses/by-sa/4.0/
-
Combinators do not make naming easy
2016 Reginald Braithwaite. Some rights reserved. 99
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 100
http://creativecommons.org/licenses/by-sa/4.0/
-
Combinators give us a language for naming things
2016 Reginald Braithwaite. Some rights reserved. 101
http://creativecommons.org/licenses/by-sa/4.0/
-
2016 Reginald Braithwaite. Some rights reserved. 102
http://creativecommons.org/licenses/by-sa/4.0/
-
Do not followin the footsteps of the sages.
2016 Reginald Braithwaite. Some rights reserved. 103
http://creativecommons.org/licenses/by-sa/4.0/
-
Seek what they sought.
2016 Reginald Braithwaite. Some rights reserved. 104
http://creativecommons.org/licenses/by-sa/4.0/
-
Reg BraithwaitePagerDuty, Inc.
raganwald.com@raganwald
2016 Reginald Braithwaite. Some rights reserved. 105
http://raganwald.comhttps://twitter.com/raganwaldhttp://creativecommons.org/licenses/by-sa/4.0/