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

F8f7496052d3bf856e944aec64cfbb99?s=128

Reg Braithwaite

January 13, 2016
Tweet

Transcript

  1. © 2016 Reginald Braithwaite. Some rights reserved. 1

  2. JavaScript Combinators the "six" edi*on © 2016 Reginald Braithwaite. Some

    rights reserved. 2
  3. © 2016 Reginald Braithwaite. Some rights reserved. 3

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

    2016 Reginald Braithwaite. Some rights reserved. 4
  5. © 2016 Reginald Braithwaite. Some rights reserved. 5

  6. and we'll think about Making responsibili/es and rela/onships explicit ©

    2016 Reginald Braithwaite. Some rights reserved. 6
  7. © 2016 Reginald Braithwaite. Some rights reserved. 7

  8. © 2016 Reginald Braithwaite. Some rights reserved. 8

  9. Decomposi)on © 2016 Reginald Braithwaite. Some rights reserved. 9

  10. We decompose en%%es to make discreet responsibili%es explicit © 2016

    Reginald Braithwaite. Some rights reserved. 10
  11. 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
  12. 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
  13. © 2016 Reginald Braithwaite. Some rights reserved. 13

  14. Composi'on © 2016 Reginald Braithwaite. Some rights reserved. 14

  15. We compose en%%es to make the rela%onships between them explicit

    © 2016 Reginald Braithwaite. Some rights reserved. 15
  16. 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
  17. © 2016 Reginald Braithwaite. Some rights reserved. 17

  18. Decomposi)on is about en))es © 2016 Reginald Braithwaite. Some rights

    reserved. 18
  19. Composi'on is about rela'onships © 2016 Reginald Braithwaite. Some rights

    reserved. 19
  20. Back to decomposi.on © 2016 Reginald Braithwaite. Some rights reserved.

    20
  21. extrac'ng named func'ons The most obvious form of decomposi2on ©

    2016 Reginald Braithwaite. Some rights reserved. 21
  22. © 2016 Reginald Braithwaite. Some rights reserved. 22

  23. Extrac'ng func'ons works from the inside-out © 2016 Reginald Braithwaite.

    Some rights reserved. 23
  24. extrac'ng named func'ons Decomposi)on of Implementa)on © 2016 Reginald Braithwaite.

    Some rights reserved. 24
  25. Let's look at something else © 2016 Reginald Braithwaite. Some

    rights reserved. 25
  26. © 2016 Reginald Braithwaite. Some rights reserved. 26

  27. 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
  28. 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
  29. func%ons have interfaces pluck's interface has two parts: The collec0on,

    and the property © 2016 Reginald Braithwaite. Some rights reserved. 29
  30. 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
  31. 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
  32. pluckFrom and pluckWith par'ally apply pluck © 2016 Reginald Braithwaite.

    Some rights reserved. 32
  33. © 2016 Reginald Braithwaite. Some rights reserved. 33

  34. Par$al applica$on decomposes func$ons from the outside-in © 2016 Reginald

    Braithwaite. Some rights reserved. 34
  35. par$al applica$on Decomposi)on of Interface © 2016 Reginald Braithwaite. Some

    rights reserved. 35
  36. © 2016 Reginald Braithwaite. Some rights reserved. 36

  37. Decorators © 2016 Reginald Braithwaite. Some rights reserved. 37

  38. 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
  39. 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
  40. 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
  41. 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
  42. © 2016 Reginald Braithwaite. Some rights reserved. 42

  43. hmmmm What is leftApply(leftApply, leftApply)? © 2016 Reginald Braithwaite. Some

    rights reserved. 43
  44. 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
  45. 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
  46. 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
  47. © 2016 Reginald Braithwaite. Some rights reserved. 47

  48. 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
  49. last one let map = (collection, fn) => collection.map(fn); let

    mapWith = C(map); let namesOf = mapWith(nameOf); © 2016 Reginald Braithwaite. Some rights reserved. 49
  50. © 2016 Reginald Braithwaite. Some rights reserved. 50

  51. 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
  52. Back to composi,on © 2016 Reginald Braithwaite. Some rights reserved.

    52
  53. © 2016 Reginald Braithwaite. Some rights reserved. 53

  54. Simple Composi+on © 2016 Reginald Braithwaite. Some rights reserved. 54

  55. let compose = (a, b) => (c) => a(b(c)); ©

    2016 Reginald Braithwaite. Some rights reserved. 55
  56. 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
  57. pluckWith = compose(mapWith, getWith); © 2016 Reginald Braithwaite. Some rights

    reserved. 57
  58. © 2016 Reginald Braithwaite. Some rights reserved. 58

  59. reminder: We compose en**es to make the rela*onships between them

    explicit © 2016 Reginald Braithwaite. Some rights reserved. 59
  60. More Composi+on © 2016 Reginald Braithwaite. Some rights reserved. 60

  61. 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
  62. 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
  63. before makes the )me rela)onship between two func)ons explicit ©

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

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

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

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

    that compose behaviour © 2016 Reginald Braithwaite. Some rights reserved. 68
  69. © 2016 Reginald Braithwaite. Some rights reserved. 69

  70. © 2016 Reginald Braithwaite. Some rights reserved. 70

  71. JavaScript invoca-ons are coloured © 2016 Reginald Braithwaite. Some rights

    reserved. 71
  72. 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
  73. Why coloured decorators ma0er © 2016 Reginald Braithwaite. Some rights

    reserved. 73
  74. 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
  75. bread, revisited class Bread { // ... make () {

    this.mix(); this.bake(); this.cool(); } } © 2016 Reginald Braithwaite. Some rights reserved. 75
  76. Classes can be decorated too © 2016 Reginald Braithwaite. Some

    rights reserved. 76
  77. 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
  78. 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
  79. 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
  80. © 2016 Reginald Braithwaite. Some rights reserved. 80

  81. Looking Forward © 2016 Reginald Braithwaite. Some rights reserved. 81

  82. ES.who-knows-when © 2016 Reginald Braithwaite. Some rights reserved. 82

  83. 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
  84. 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
  85. be#er make class Bread { // ... @invokeBefore('mix') @invokeAfter('cool') make

    () { this.bake(); } } © 2016 Reginald Braithwaite. Some rights reserved. 85
  86. © 2016 Reginald Braithwaite. Some rights reserved. 86

  87. What have we seen so far? © 2016 Reginald Braithwaite.

    Some rights reserved. 87
  88. 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
  89. What have we seen so far? • Simple composi,on •

    Composi,on decorators • Class decorators • Method decorators © 2016 Reginald Braithwaite. Some rights reserved. 89
  90. Don't worry about the details! © 2016 Reginald Braithwaite. Some

    rights reserved. 90
  91. © 2016 Reginald Braithwaite. Some rights reserved. 91

  92. it's all the same idea Decomposi)on makes responsibili)es explicit ©

    2016 Reginald Braithwaite. Some rights reserved. 92
  93. © 2016 Reginald Braithwaite. Some rights reserved. 93

  94. and it's all the same idea Composi'on makes rela'onships explicit

    © 2016 Reginald Braithwaite. Some rights reserved. 94
  95. These ideas ma*er © 2016 Reginald Braithwaite. Some rights reserved.

    95
  96. There are only two hard problems in Computer Science: Cache

    invalida9on, and naming things. © 2016 Reginald Braithwaite. Some rights reserved. 96
  97. © 2016 Reginald Braithwaite. Some rights reserved. 97

  98. 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
  99. © 2016 Reginald Braithwaite. Some rights reserved. 99

  100. 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
  101. © 2016 Reginald Braithwaite. Some rights reserved. 101

  102. Combinators do not make naming easy © 2016 Reginald Braithwaite.

    Some rights reserved. 102
  103. © 2016 Reginald Braithwaite. Some rights reserved. 103

  104. Combinators give us a language for naming things in code

    © 2016 Reginald Braithwaite. Some rights reserved. 104
  105. © 2016 Reginald Braithwaite. Some rights reserved. 105

  106. Do not follow in the footsteps of the sages. ©

    2016 Reginald Braithwaite. Some rights reserved. 106
  107. Seek what they sought. © 2016 Reginald Braithwaite. Some rights

    reserved. 107
  108. Reg Braithwaite PagerDuty, Inc. raganwald.com @raganwald © 2016 Reginald Braithwaite.

    Some rights reserved. 108