Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
JavaScript Combinators, the “six” edition
Search
Reg Braithwaite
January 13, 2016
Technology
1.5k
8
Share
JavaScript Combinators, the “six” edition
Presented at NDC London, January 13, 2016
Reg Braithwaite
January 13, 2016
More Decks by Reg Braithwaite
See All by Reg Braithwaite
Courage
raganwald
0
150
Waste in Software Development
raganwald
0
200
First-Class Commands, the 2017 Edition
raganwald
1
230
Optimism and the Growth Mindset
raganwald
0
330
ember-concurrency: an experience report
raganwald
1
160
Optimism II
raganwald
0
430
Optimism
raganwald
0
2k
First-Class Commands: an unexpectedly fertile design pattern
raganwald
4
3k
Duck Typing, Compatibility, and the Adaptor Pattern
raganwald
7
11k
Other Decks in Technology
See All in Technology
AIが盛んな時代に 技術記事を書き始めて起きた私の中での小さな変化
peintangos
0
360
カオナビに Suspenseを導入するまで / The Road to Suspense at kaonavi
kaonavi
1
430
AI駆動開発で生産性を追いかけたら、行き着いたのは品質とシフトレフトだった
littlehands
0
390
世界の中心でApp Runnerを叫ぶ FINAL
tsukuboshi
0
240
ServiceによるKubernetes通信制御ーClusterIPを例に
miku01
1
140
古今東西SRE
okaru
1
130
バイブコーディングで3倍早く⚪⚪を作ってみた
samakada
0
220
AI時代に越境し、 組織を変えるQAスキルの正体 / QA Skills for Transforming an Organization
mii3king
5
4k
拝啓、あの夏の僕へ〜あなたも知っているApp Runnerの世界〜
news_it_enj
0
210
音声言語モデル手法に関する発表の紹介
kzinmr
0
160
生成AIが変える SaaS の競争原理と弁護士ドットコムのプロダクト戦略
bengo4com
1
3.5k
毎日の作業を Claude Code 経由にしたら、 ノウハウがコードになった
kossykinto
1
870
Featured
See All Featured
Designing for Performance
lara
611
70k
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.2k
The agentic SEO stack - context over prompts
schlessera
0
770
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
200
Building the Perfect Custom Keyboard
takai
2
750
Documentation Writing (for coders)
carmenintech
77
5.3k
How to Think Like a Performance Engineer
csswizardry
28
2.6k
Being A Developer After 40
akosma
91
590k
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
110
Code Review Best Practice
trishagee
74
20k
Ethics towards AI in product and experience design
skipperchong
2
270
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
231
23k
Transcript
© 2016 Reginald Braithwaite. Some rights reserved. 1
JavaScript Combinators the "six" edi*on © 2016 Reginald Braithwaite. Some
rights reserved. 2
© 2016 Reginald Braithwaite. Some rights reserved. 3
we'll talk about Using combinators for decomposi2on and composi2on ©
2016 Reginald Braithwaite. Some rights reserved. 4
© 2016 Reginald Braithwaite. Some rights reserved. 5
and we'll think about Making responsibili/es and rela/onships explicit ©
2016 Reginald Braithwaite. Some rights reserved. 6
© 2016 Reginald Braithwaite. Some rights reserved. 7
© 2016 Reginald Braithwaite. Some rights reserved. 8
Decomposi)on © 2016 Reginald Braithwaite. Some rights reserved. 9
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
© 2016 Reginald Braithwaite. Some rights reserved. 13
Composi'on © 2016 Reginald Braithwaite. Some rights reserved. 14
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
© 2016 Reginald Braithwaite. Some rights reserved. 17
Decomposi)on is about en))es © 2016 Reginald Braithwaite. Some rights
reserved. 18
Composi'on is about rela'onships © 2016 Reginald Braithwaite. Some rights
reserved. 19
Back to decomposi.on © 2016 Reginald Braithwaite. Some rights reserved.
20
extrac'ng named func'ons The most obvious form of decomposi2on ©
2016 Reginald Braithwaite. Some rights reserved. 21
© 2016 Reginald Braithwaite. Some rights reserved. 22
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
© 2016 Reginald Braithwaite. Some rights reserved. 26
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
© 2016 Reginald Braithwaite. Some rights reserved. 33
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
© 2016 Reginald Braithwaite. Some rights reserved. 36
Decorators © 2016 Reginald Braithwaite. Some rights reserved. 37
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
© 2016 Reginald Braithwaite. Some rights reserved. 42
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
© 2016 Reginald Braithwaite. Some rights reserved. 47
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
© 2016 Reginald Braithwaite. Some rights reserved. 50
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
Back to composi,on © 2016 Reginald Braithwaite. Some rights reserved.
52
© 2016 Reginald Braithwaite. Some rights reserved. 53
Simple Composi+on © 2016 Reginald Braithwaite. Some rights reserved. 54
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
pluckWith = compose(mapWith, getWith); © 2016 Reginald Braithwaite. Some rights
reserved. 57
© 2016 Reginald Braithwaite. Some rights reserved. 58
reminder: We compose en**es to make the rela*onships between them
explicit © 2016 Reginald Braithwaite. Some rights reserved. 59
More Composi+on © 2016 Reginald Braithwaite. Some rights reserved. 60
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
© 2016 Reginald Braithwaite. Some rights reserved. 69
© 2016 Reginald Braithwaite. Some rights reserved. 70
JavaScript invoca-ons are coloured © 2016 Reginald Braithwaite. Some rights
reserved. 71
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
Why coloured decorators ma0er © 2016 Reginald Braithwaite. Some rights
reserved. 73
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
© 2016 Reginald Braithwaite. Some rights reserved. 80
Looking Forward © 2016 Reginald Braithwaite. Some rights reserved. 81
ES.who-knows-when © 2016 Reginald Braithwaite. Some rights reserved. 82
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
© 2016 Reginald Braithwaite. Some rights reserved. 86
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
© 2016 Reginald Braithwaite. Some rights reserved. 91
it's all the same idea Decomposi)on makes responsibili)es explicit ©
2016 Reginald Braithwaite. Some rights reserved. 92
© 2016 Reginald Braithwaite. Some rights reserved. 93
and it's all the same idea Composi'on makes rela'onships explicit
© 2016 Reginald Braithwaite. Some rights reserved. 94
These ideas ma*er © 2016 Reginald Braithwaite. Some rights reserved.
95
There are only two hard problems in Computer Science: Cache
invalida9on, and naming things. © 2016 Reginald Braithwaite. Some rights reserved. 96
© 2016 Reginald Braithwaite. Some rights reserved. 97
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
© 2016 Reginald Braithwaite. Some rights reserved. 99
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
© 2016 Reginald Braithwaite. Some rights reserved. 101
Combinators do not make naming easy © 2016 Reginald Braithwaite.
Some rights reserved. 102
© 2016 Reginald Braithwaite. Some rights reserved. 103
Combinators give us a language for naming things in code
© 2016 Reginald Braithwaite. Some rights reserved. 104
© 2016 Reginald Braithwaite. Some rights reserved. 105
Do not follow in the footsteps of the sages. ©
2016 Reginald Braithwaite. Some rights reserved. 106
Seek what they sought. © 2016 Reginald Braithwaite. Some rights
reserved. 107
Reg Braithwaite PagerDuty, Inc. raganwald.com @raganwald © 2016 Reginald Braithwaite.
Some rights reserved. 108