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
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Reg Braithwaite
January 13, 2016
Technology
8
1.5k
JavaScript Combinators, the “six” edition
Presented at NDC London, January 13, 2016
Reg Braithwaite
January 13, 2016
Tweet
Share
More Decks by Reg Braithwaite
See All by Reg Braithwaite
Courage
raganwald
0
140
Waste in Software Development
raganwald
0
190
First-Class Commands, the 2017 Edition
raganwald
1
210
Optimism and the Growth Mindset
raganwald
0
310
ember-concurrency: an experience report
raganwald
1
150
Optimism II
raganwald
0
420
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
今日から始めるAmazon Bedrock AgentCore
har1101
4
400
Frontier Agents (Kiro autonomous agent / AWS Security Agent / AWS DevOps Agent) の紹介
msysh
3
150
CDKで始めるTypeScript開発のススメ
tsukuboshi
1
340
Introduction to Bill One Development Engineer
sansan33
PRO
0
360
顧客との商談議事録をみんなで読んで顧客解像度を上げよう
shibayu36
0
190
茨城の思い出を振り返る ~CDKのセキュリティを添えて~ / 20260201 Mitsutoshi Matsuo
shift_evolve
PRO
1
200
30万人の同時アクセスに耐えたい!新サービスの盤石なリリースを支える負荷試験 / SRE Kaigi 2026
genda
1
420
レガシー共有バッチ基盤への挑戦 - SREドリブンなリアーキテクチャリングの取り組み
tatsukoni
0
210
Agile Leadership Summit Keynote 2026
m_seki
1
530
広告の効果検証を題材にした因果推論の精度検証について
zozotech
PRO
0
140
Contract One Engineering Unit 紹介資料
sansan33
PRO
0
13k
MCPでつなぐElasticsearchとLLM - 深夜の障害対応を楽にしたい / Bridging Elasticsearch and LLMs with MCP
sashimimochi
0
150
Featured
See All Featured
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
0
200
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
380
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
The SEO Collaboration Effect
kristinabergwall1
0
350
The Pragmatic Product Professional
lauravandoore
37
7.1k
ラッコキーワード サービス紹介資料
rakko
1
2.2M
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
320
Six Lessons from altMBA
skipperchong
29
4.1k
Building AI with AI
inesmontani
PRO
1
680
The #1 spot is gone: here's how to win anyway
tamaranovitovic
2
930
For a Future-Friendly Web
brad_frost
182
10k
WENDY [Excerpt]
tessaabrams
9
36k
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