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
8
1.3k
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
100
Waste in Software Development
raganwald
0
160
First-Class Commands, the 2017 Edition
raganwald
1
160
Optimism and the Growth Mindset
raganwald
0
260
ember-concurrency: an experience report
raganwald
1
120
Optimism II
raganwald
0
370
Optimism
raganwald
0
1.9k
First-Class Commands: an unexpectedly fertile design pattern
raganwald
4
2.7k
Duck Typing, Compatibility, and the Adaptor Pattern
raganwald
7
10k
Other Decks in Technology
See All in Technology
プロダクトチームへのSystem Risk Records導入・運用事例の紹介/Introduction and Case Studies on Implementing and Operating System Risk Records for Product Teams
taddy_919
1
180
生成AIとAWS CDKで実現! 自社ブログレビューの効率化
ymae
2
330
「 SharePoint 難しい」ってよく聞くけど、そんなに言うなら8歳の息子に試してもらった
taichinakamura
1
660
バクラクにおける可観測性向上の取り組み
yuu26
3
430
CyberAgent 生成AI Deep Dive with Amazon Web Services / genai-aws
cyberagentdevelopers
PRO
1
480
CAMERA-Suite: 広告文生成のための評価スイート / ai-camera-suite
cyberagentdevelopers
PRO
3
270
いまならこう作りたい AWSコンテナ[本格]入門ハンズオン 〜2024年版 ハンズオンの構想〜
horsewin
9
2.1k
運用イベント対応への生成AIの活用 with Failure Analysis Assistant
suzukyz
0
130
プロポーザルのつくり方 〜個人技編〜 / How to come up with proposals
ohbarye
2
160
新卒1年目が挑む!生成AI × マルチエージェントで実現する次世代オンボーディング / operation-ai-onboarding
cyberagentdevelopers
PRO
1
170
プロダクトエンジニアが活躍する環境を作りたくて 事業責任者になった話 ~プロダクトエンジニアの行き着く先~
gimupop
1
500
リンクアンドモチベーション ソフトウェアエンジニア向け紹介資料 / Introduction to Link and Motivation for Software Engineers
lmi
4
290k
Featured
See All Featured
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
328
21k
YesSQL, Process and Tooling at Scale
rocio
167
14k
5 minutes of I Can Smell Your CMS
philhawksworth
202
19k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
107
49k
Java REST API Framework Comparison - PWX 2021
mraible
PRO
28
7.9k
The Power of CSS Pseudo Elements
geoffreycrofte
72
5.3k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
167
49k
KATA
mclloyd
29
13k
A designer walks into a library…
pauljervisheath
202
24k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
31
2.7k
No one is an island. Learnings from fostering a developers community.
thoeni
19
3k
What's new in Ruby 2.0
geeforr
342
31k
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