Share
Embed
JavaScript Combinators, the “six” edition
Presented at NDC London, January 13, 2016
Reg Braithwaite
January 13, 2016
Transcript
108 slides
-
JavaScript Combinators the "six" edi*on © 2016 Reginald Braithwaite. Some
rights reserved. 2 -
we'll talk about Using combinators for decomposi2on and composi2on ©
2016 Reginald Braithwaite. Some rights reserved. 4 -
and we'll think about Making responsibili/es and rela/onships explicit ©
2016 Reginald Braithwaite. Some rights reserved. 6 -
We decompose en%%es to make discreet responsibili%es explicit © 2016
Reginald Braithwaite. Some rights reserved. 10 -
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 -
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 -
We compose en%%es to make the rela%onships between them explicit
© 2016 Reginald Braithwaite. Some rights reserved. 15 -
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 -
extrac'ng named func'ons The most obvious form of decomposi2on ©
2016 Reginald Braithwaite. Some rights reserved. 21 -
Extrac'ng func'ons works from the inside-out © 2016 Reginald Braithwaite.
Some rights reserved. 23 -
extrac'ng named func'ons Decomposi)on of Implementa)on © 2016 Reginald Braithwaite.
Some rights reserved. 24 -
Let's look at something else © 2016 Reginald Braithwaite. Some
rights reserved. 25 -
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 -
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 -
func%ons have interfaces pluck's interface has two parts: The collec0on,
and the property © 2016 Reginald Braithwaite. Some rights reserved. 29 -
manually decomposing pluck's interface var deStijl = [ {name: 'Theo
van Doesburg', occupation: 'theorist'}, {name: 'Piet Mondriaan', occupation: 'painter'}, {name: 'Gerrit Rietveld', occupation: 'architect'}]; let pluckFrom = (collection) => (property) => pluck(collection, property); pluckFrom(deStijl)('name') //=> ["Theo van Doesburg", "Piet Mondriaan", "Gerrit Rietveld"] © 2016 Reginald Braithwaite. Some rights reserved. 30 -
manually decomposing pluck's interface let pluckWith = (property) => (collection)
=> pluck(collection, property); pluckWith('name')(deStijl) //=> ["Theo van Doesburg", "Piet Mondriaan", "Gerrit Rietveld"] © 2016 Reginald Braithwaite. Some rights reserved. 31 -
pluckFrom and pluckWith par'ally apply pluck © 2016 Reginald Braithwaite.
Some rights reserved. 32 -
Par$al applica$on decomposes func$ons from the outside-in © 2016 Reginald
Braithwaite. Some rights reserved. 34 -
par$al applica$on Decomposi)on of Interface © 2016 Reginald Braithwaite. Some
rights reserved. 35 -
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 -
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 -
extract closed-over binding let pluckFrom = (collection) => (property) =>
pluck(collection, property); // extract `pluck`: let ____ = (pluck, collection) => (property) => pluck(collection, property); // rename: let leftApply = (fn, a) => (b) => fn(a, b); © 2016 Reginald Braithwaite. Some rights reserved. 40 -
using leftApply let pluckFrom = (collection) => leftApply(pluck, collection); //
again: let pluckFrom = leftApply(leftApply, pluck); // again let pluckFrom = leftApply(leftApply, leftApply)(pluck); © 2016 Reginald Braithwaite. Some rights reserved. 41 -
hmmmm What is leftApply(leftApply, leftApply)? © 2016 Reginald Braithwaite. Some
rights reserved. 43 -
back to par*al applica*on let rightApply = (fn, b) =>
(a) => fn(a, b); let pluckWith = (property) => (pluck, property); // again: let pluckWith = leftApply(rightApply, pluck); // again: let pluckWith = leftApply(leftApply, rightApply)(pluck); © 2016 Reginald Braithwaite. Some rights reserved. 44 -
combinators that decompose func2ons // leftApply(leftApply, leftApply) let Istarstar =
(a) => (b) => (c) => a(b, c); let pluckFrom = Istarstar(pluck); // leftApply(leftApply, rightApply) let C = (a) => (b) => (c) => a(c, b) let pluckWith = C(pluck); © 2016 Reginald Braithwaite. Some rights reserved. 45 -
more decomposi+on with combinators let get = (object, property) =>
object[property]; get({name: 'Gerrit Rietveld'}, 'name') //=> Gerrit Rietveld let getWith = C(get); let nameOf = getWith('name'); nameOf({name: 'Gerrit Rietveld'}) //=> Gerrit Rietveld © 2016 Reginald Braithwaite. Some rights reserved. 46 -
get is a func)on taking two arguments getWith is a
decomposi+on of get that names one part nameOf is a decomposi+on of get that names and specifies one part © 2016 Reginald Braithwaite. Some rights reserved. 48 -
last one let map = (collection, fn) => collection.map(fn); let
mapWith = C(map); let namesOf = mapWith(nameOf); © 2016 Reginald Braithwaite. Some rights reserved. 49 -
map is a higher-order func0on mapWith is a decomposi+on of
map that names one part namesOf is a decomposi+on of map that names and specifies one part © 2016 Reginald Braithwaite. Some rights reserved. 51 -
let compose = (a, b) => (c) => a(b(c)); ©
2016 Reginald Braithwaite. Some rights reserved. 55 -
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. 56 -
reminder: We compose en**es to make the rela*onships between them
explicit © 2016 Reginald Braithwaite. Some rights reserved. 59 -
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. 61 -
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. 62 -
before makes the )me rela)onship between two func)ons explicit ©
2016 Reginald Braithwaite. Some rights reserved. 63 -
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. 64 -
after also makes the +me rela+onship between two func+ons explicit
© 2016 Reginald Braithwaite. Some rights reserved. 65 -
decomposing before let beforeWith = (decoration) => rightApply(before, decoration); let
mixBefore = beforeWith(mix); let bakeBread = mixBefore(bake); © 2016 Reginald Braithwaite. Some rights reserved. 66 -
decomposing after let afterWith = (decoration) => rightApply(after, decoration); let
coolAfter = afterWith(after); let makeBread = coolAfter(bakeBread); © 2016 Reginald Braithwaite. Some rights reserved. 67 -
beforeWith and afterWith are combinators that turn func1ons into decorators
that compose behaviour © 2016 Reginald Braithwaite. Some rights reserved. 68 -
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. 72 -
bread, revisited class Bread { constructor (...ingredients) { this.ingredients =
ingredients; } mix () { console.log('mixing', ...this.ingredients); }; bake () { console.log('baking'); } cool () { console.log('cooling'); } } © 2016 Reginald Braithwaite. Some rights reserved. 74 -
bread, revisited class Bread { // ... make () {
this.mix(); this.bake(); this.cool(); } } © 2016 Reginald Braithwaite. Some rights reserved. 75 -
Classes can be decorated too © 2016 Reginald Braithwaite. Some
rights reserved. 76 -
decorateMethodWith const decorateMethodWith = (decorator, ...methodNames) => (clazz) => {
for (let methodName of methodNames) { const method = clazz.prototype[methodName]; Object.defineProperty(clazz.prototype, methodName, { value: decorator(method), writable: true }); } return clazz; }; © 2016 Reginald Braithwaite. Some rights reserved. 77 -
beforeAll and afterAll const beforeAll = (decorator, ...methodNames) => decorateMethodWith((method)
=> before(method, decorator), ...methodNames), afterAll = (decorator, ...methodNames) => decorateMethodWith((method) => after(method, decorator), ...methodNames); © 2016 Reginald Braithwaite. Some rights reserved. 78 -
be#er bread let invoke = (methodName) => function (...args) {
return this[methodName](...args); } let BetterBread = beforeAll(invoke('mix'), 'make')( afterAll(invoke('cool'), 'make')( class { // ... make () { this.bake(); } } ) ); © 2016 Reginald Braithwaite. Some rights reserved. 79 -
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 AwesomeBread { // ... make () { this.bake(); } } © 2016 Reginald Braithwaite. Some rights reserved. 83 -
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. 84 -
be#er make class Bread { // ... @invokeBefore('mix') @invokeAfter('cool') make
() { this.bake(); } } © 2016 Reginald Braithwaite. Some rights reserved. 85 -
What have we seen so far? © 2016 Reginald Braithwaite.
Some rights reserved. 87 -
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. 88 -
What have we seen so far? • Simple composi,on •
Composi,on decorators • Class decorators • Method decorators © 2016 Reginald Braithwaite. Some rights reserved. 89 -
Don't worry about the details! © 2016 Reginald Braithwaite. Some
rights reserved. 90 -
it's all the same idea Decomposi)on makes responsibili)es explicit ©
2016 Reginald Braithwaite. Some rights reserved. 92 -
and it's all the same idea Composi'on makes rela'onships explicit
© 2016 Reginald Braithwaite. Some rights reserved. 94 -
There are only two hard problems in Computer Science: Cache
invalida9on, and naming things. © 2016 Reginald Braithwaite. Some rights reserved. 96 -
Naming en))es is hard because you have to figure out
which en))es need to be named © 2016 Reginald Braithwaite. Some rights reserved. 98 -
Naming rela+onships is hard because you have to figure out
which rela+onships need to be named © 2016 Reginald Braithwaite. Some rights reserved. 100 -
Combinators do not make naming easy © 2016 Reginald Braithwaite.
Some rights reserved. 102 -
Combinators give us a language for naming things in code
© 2016 Reginald Braithwaite. Some rights reserved. 104 -
Do not follow in the footsteps of the sages. ©
2016 Reginald Braithwaite. Some rights reserved. 106 -
Reg Braithwaite PagerDuty, Inc. raganwald.com @raganwald © 2016 Reginald Braithwaite.
Some rights reserved. 108