Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JavaScript Combinators, the “six” edition

JavaScript Combinators, the “six” edition

Presented at NDC London, January 13, 2016

Reg Braithwaite

January 13, 2016
Tweet

More Decks by Reg Braithwaite

Other Decks in Technology

Transcript

  1. we'll talk about Using combinators for decomposi2on and composi2on ©

    2016 Reginald Braithwaite. Some rights reserved. 4
  2. and we'll think about Making responsibili/es and rela/onships explicit ©

    2016 Reginald Braithwaite. Some rights reserved. 6
  3. We decompose en%%es to make discreet responsibili%es explicit © 2016

    Reginald Braithwaite. Some rights reserved. 10
  4. 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
  5. 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
  6. We compose en%%es to make the rela%onships between them explicit

    © 2016 Reginald Braithwaite. Some rights reserved. 15
  7. 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
  8. extrac'ng named func'ons The most obvious form of decomposi2on ©

    2016 Reginald Braithwaite. Some rights reserved. 21
  9. 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
  10. 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
  11. func%ons have interfaces pluck's interface has two parts: The collec0on,

    and the property © 2016 Reginald Braithwaite. Some rights reserved. 29
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. last one let map = (collection, fn) => collection.map(fn); let

    mapWith = C(map); let namesOf = mapWith(nameOf); © 2016 Reginald Braithwaite. Some rights reserved. 49
  23. 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
  24. let compose = (a, b) => (c) => a(b(c)); ©

    2016 Reginald Braithwaite. Some rights reserved. 55
  25. 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
  26. reminder: We compose en**es to make the rela*onships between them

    explicit © 2016 Reginald Braithwaite. Some rights reserved. 59
  27. 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
  28. 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
  29. before makes the )me rela)onship between two func)ons explicit ©

    2016 Reginald Braithwaite. Some rights reserved. 63
  30. 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
  31. after also makes the +me rela+onship between two func+ons explicit

    © 2016 Reginald Braithwaite. Some rights reserved. 65
  32. decomposing before let beforeWith = (decoration) => rightApply(before, decoration); let

    mixBefore = beforeWith(mix); let bakeBread = mixBefore(bake); © 2016 Reginald Braithwaite. Some rights reserved. 66
  33. decomposing after let afterWith = (decoration) => rightApply(after, decoration); let

    coolAfter = afterWith(after); let makeBread = coolAfter(bakeBread); © 2016 Reginald Braithwaite. Some rights reserved. 67
  34. beforeWith and afterWith are combinators that turn func1ons into decorators

    that compose behaviour © 2016 Reginald Braithwaite. Some rights reserved. 68
  35. 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
  36. 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
  37. bread, revisited class Bread { // ... make () {

    this.mix(); this.bake(); this.cool(); } } © 2016 Reginald Braithwaite. Some rights reserved. 75
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. be#er make class Bread { // ... @invokeBefore('mix') @invokeAfter('cool') make

    () { this.bake(); } } © 2016 Reginald Braithwaite. Some rights reserved. 85
  44. 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
  45. What have we seen so far? • Simple composi,on •

    Composi,on decorators • Class decorators • Method decorators © 2016 Reginald Braithwaite. Some rights reserved. 89
  46. it's all the same idea Decomposi)on makes responsibili)es explicit ©

    2016 Reginald Braithwaite. Some rights reserved. 92
  47. and it's all the same idea Composi'on makes rela'onships explicit

    © 2016 Reginald Braithwaite. Some rights reserved. 94
  48. There are only two hard problems in Computer Science: Cache

    invalida9on, and naming things. © 2016 Reginald Braithwaite. Some rights reserved. 96
  49. 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
  50. 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
  51. Combinators give us a language for naming things in code

    © 2016 Reginald Braithwaite. Some rights reserved. 104
  52. Do not follow in the footsteps of the sages. ©

    2016 Reginald Braithwaite. Some rights reserved. 106