Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
First-Class Commands, the 2017 Edition
Search
Reg Braithwaite
June 15, 2017
Technology
250
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
First-Class Commands, the 2017 Edition
NDC Oslo June 15, 2017
Reg Braithwaite
June 15, 2017
More Decks by Reg Braithwaite
See All by Reg Braithwaite
Courage
raganwald
0
170
Waste in Software Development
raganwald
0
220
Optimism and the Growth Mindset
raganwald
0
360
ember-concurrency: an experience report
raganwald
1
170
Optimism II
raganwald
0
450
Optimism
raganwald
0
2.1k
First-Class Commands: an unexpectedly fertile design pattern
raganwald
4
3k
JavaScript Combinators, the “six” edition
raganwald
8
1.5k
Duck Typing, Compatibility, and the Adaptor Pattern
raganwald
7
11k
Other Decks in Technology
See All in Technology
AIに書かせて、プラットフォームで縛る ― EKSプラットフォームで実践した責任境界と権限設計
elmodev09
1
1.1k
【Findyテック文化祭ワークショップ】新卒エンジニア&採用担当と作る、 なりたい姿と今やるべき一歩
dip_tech
PRO
0
150
1人アドミンな私はAWSアカウント申請をSlackで完結したい!
ysuzuki
0
100
Claude Designがめちゃくちゃ便利なので使ってほしい
diggymo
0
170
企業の現実世界をグラフで写し取る
sansantech
PRO
0
210
20260930_Gemma4_Hands-on
tsho
0
170
Apache Iceberg が拓く AI 時代のオープンレイクハウス
tomtanaka
0
200
営業オントロジーの作り方と、エージェントからの辿り方 ── ナレッジワークの現場から
kworkdev
PRO
1
200
「今盗んで、後で解く」に備える ― AWSのポスト量子暗号入門
yama3133
2
280
認知負荷を吸収し、プロダクトをまたぐPR Preview基盤の設計事例
taiki45
2
550
AI coding 整合正規方法
philipz
0
580
HolmesGPTで始めるSREエージェント入門!プラットフォームの障害調査はAIにお任せ 〜
leveragestech
PRO
0
120
Featured
See All Featured
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
380
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
390
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
250
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.4k
Ten Tips & Tricks for a 🌱 transition
stuffmc
1
240
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
500
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.4k
Google's AI Overviews - The New Search
badams
0
1.6k
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
2
2.9k
Prompt Engineering for Job Search
mfonobong
0
460
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
2
290
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
Transcript
© 2017 Reginald Braithwaite. Some rights reserved. 1
First-Class Commands an unexpectedly fertile design pa!ern © 2017 Reginald
Braithwaite. Some rights reserved. 2
why do we care about commands? © 2017 Reginald Braithwaite.
Some rights reserved. 3
© 2017 Reginald Braithwaite. Some rights reserved. 4
the canonical example: mutable data © 2017 Reginald Braithwaite. Some
rights reserved. 5
class Buffer { constructor (text = '') { this.text =
text; } replaceWith (replacement, from = 0, to = this.text.length) { this.text = this.text.slice(0, from) + replacement + this.text.slice(to); return this; } toString () { return this.text; } } © 2017 Reginald Braithwaite. Some rights reserved. 6
let buffer = new Buffer(); buffer.replaceWith( "The quick brown fox
jumped over the lazy dog" ); buffer.replaceWith("fast", 4, 9); buffer.replaceWith("canine", 40, 43); //=> The fast brown fox jumped over the lazy canine © 2017 Reginald Braithwaite. Some rights reserved. 7
buffer is an object © 2017 Reginald Braithwaite. Some rights
reserved. 8
we treat objects as first-class entities © 2017 Reginald Braithwaite.
Some rights reserved. 9
replaceWith is a method © 2017 Reginald Braithwaite. Some rights
reserved. 10
we can treat methods as first- class entities © 2017
Reginald Braithwaite. Some rights reserved. 11
buffer.replaceWith("fast", 4, 9) is an invocation © 2017 Reginald Braithwaite.
Some rights reserved. 12
what does it mean to treat an invocation as a
first-class entity? © 2017 Reginald Braithwaite. Some rights reserved. 13
storing invocations © 2017 Reginald Braithwaite. Some rights reserved. 14
class Edit { constructor (buffer, {replacement, from, to}) { this.buffer
= buffer; Object.assign(this, {replacement, from, to}); } doIt () { this.buffer.text = this.buffer.text.slice(0, this.from) + this.replacement + this.buffer.text.slice(this.to); return this.buffer; } } © 2017 Reginald Braithwaite. Some rights reserved. 15
class Buffer { constructor (text = '') { this.text =
text; } replaceWith (replacement, from = 0, to = this.text.length) { return new Edit(this, {replacement, from, to}); } toString () { return this.text; } } © 2017 Reginald Braithwaite. Some rights reserved. 16
let buffer = new Buffer(), jobQueue = []; jobQueue.push( buffer.replaceWith(
"The quick brown fox jumped over the lazy dog" ) ); jobQueue.push( buffer.replaceWith("fast", 4, 9) ); jobQueue.push( buffer.replaceWith("canine", 40, 43) ); while (jobQueue.length > 0) { jobQueue.shift().doIt(); } //=> The fast brown fox jumped over the lazy canine © 2017 Reginald Braithwaite. Some rights reserved. 17
job queues © 2017 Reginald Braithwaite. Some rights reserved. 18
© 2017 Reginald Braithwaite. Some rights reserved. 19
querying invocations © 2017 Reginald Braithwaite. Some rights reserved. 20
class Edit { netChange () { return this.from - this.to
+ this.replacement.length; } } © 2017 Reginald Braithwaite. Some rights reserved. 21
let buffer = new Buffer(); buffer.replaceWith( "The quick brown fox
jumped over the lazy dog" ).netChange(); //=> 44 buffer.replaceWith("fast", 4, 9).netChange(); //=> -1 © 2017 Reginald Braithwaite. Some rights reserved. 22
command state © 2017 Reginald Braithwaite. Some rights reserved. 23
© 2017 Reginald Braithwaite. Some rights reserved. 24
command history © 2017 Reginald Braithwaite. Some rights reserved. 25
© 2017 Reginald Braithwaite. Some rights reserved. 26
transforming invocations © 2017 Reginald Braithwaite. Some rights reserved. 27
class Edit { reversed () { let replacement = this.buffer.text.slice(this.from,
this.to), from = this.from, to = from + this.replacement.length; return new Edit(buffer, {replacement, from, to}); } } © 2017 Reginald Braithwaite. Some rights reserved. 28
let buffer = new Buffer( "The quick brown fox jumped
over the lazy dog" ); let doer = buffer.replaceWith("fast", 4, 9), undoer = doer.reversed(); doer.doIt(); //=> The fast brown fox jumped over the lazy dog undoer.doIt(); //=> The quick brown fox jumped over the lazy dog © 2017 Reginald Braithwaite. Some rights reserved. 29
all together now © 2017 Reginald Braithwaite. Some rights reserved.
30
class Buffer { constructor (text = '') { this.text =
text; this.history = []; this.future = []; } } © 2017 Reginald Braithwaite. Some rights reserved. 31
class Buffer { replaceWith (replacement, from = 0, to =
this.length()) { let doer = new Edit(this, {replacement, from, to}), undoer = doer.reversed(); this.history.push(undoer); this.future = []; return doer.doIt(); } } © 2017 Reginald Braithwaite. Some rights reserved. 32
undo © 2017 Reginald Braithwaite. Some rights reserved. 33
class Buffer { undo () { let undoer = this.history.pop(),
redoer = undoer.reversed(); this.future.unshift(redoer); return undoer.doIt(); } } © 2017 Reginald Braithwaite. Some rights reserved. 34
let buffer = new Buffer( "The quick brown fox jumped
over the lazy dog" ); buffer.replaceWith("fast", 4, 9) //=> The fast brown fox jumped over the lazy dog buffer.replaceWith("canine", 40, 43) //=> The fast brown fox jumped over the lazy canine © 2017 Reginald Braithwaite. Some rights reserved. 35
buffer.undo() //=> The fast brown fox jumped over the lazy
dog buffer.undo() //=> The quick brown fox jumped over the lazy dog © 2017 Reginald Braithwaite. Some rights reserved. 36
redo © 2017 Reginald Braithwaite. Some rights reserved. 37
class Buffer { redo () { let redoer = this.future.shift(),
undoer = redoer.reversed(); this.history.push(undoer); return redoer.doIt(); } } © 2017 Reginald Braithwaite. Some rights reserved. 38
buffer.redo() //=> The fast brown fox jumped over the lazy
dog buffer.redo() //=> The fast brown fox jumped over the lazy canine © 2017 Reginald Braithwaite. Some rights reserved. 39
that's the basic command pa!ern © 2017 Reginald Braithwaite. Some
rights reserved. 40
invocations as first-class entities: we stored them; we queried them;
we transformed them. © 2017 Reginald Braithwaite. Some rights reserved. 41
question! © 2017 Reginald Braithwaite. Some rights reserved. 42
class Buffer { replaceWith (replacement, from = 0, to =
this.length()) { let doer = new Edit(this, {replacement, from, to}), undoer = doer.reversed(); this.history.push(undoer); this.future = []; return doer.doIt(); } } © 2017 Reginald Braithwaite. Some rights reserved. 43
why do we have to throw the future away? ©
2017 Reginald Braithwaite. Some rights reserved. 44
class Buffer { replaceWith (replacement, from = 0, to =
this.length()) { let doer = new Edit(this, {replacement, from, to}), undoer = doer.reversed(); this.history.push(undoer); // this.future = []; return doer.doIt(); } } © 2017 Reginald Braithwaite. Some rights reserved. 45
let buffer = new Buffer( "The quick brown fox jumped
over the lazy dog" ); buffer.replaceWith("fast", 4, 9); //=> The fast brown fox jumped over the lazy dog buffer.undo(); //=> The quick brown fox jumped over the lazy dog buffer.replaceWith("My", 0, 3); //=> My quick brown fox jumped over the lazy dog © 2017 Reginald Braithwaite. Some rights reserved. 46
what happens when we evaluate buffer.redo()? © 2017 Reginald Braithwaite.
Some rights reserved. 47
"My qfastbrown fox jumped over the lazy dog" © 2017
Reginald Braithwaite. Some rights reserved. 48
© 2017 Reginald Braithwaite. Some rights reserved. 49
let's consider commands as a history © 2017 Reginald Braithwaite.
Some rights reserved. 50
let buffer = new Buffer("The quick brown fox jumped over
the lazy dog"); "The quick brown fox jumped over the lazy dog" // PAST // FUTURE © 2017 Reginald Braithwaite. Some rights reserved. 51
buffer.replaceWith("fast", 4, 9) "The fast brown fox jumped over the
lazy dog" // PAST replaceWith("fast", 4, 9) // FUTURE © 2017 Reginald Braithwaite. Some rights reserved. 52
buffer.undo() "The quick brown fox jumped over the lazy dog"
// PAST // FUTURE replaceWith("fast", 4, 9) © 2017 Reginald Braithwaite. Some rights reserved. 53
buffer.replaceWith("My", 0, 3) "My quick brown fox jumped over the
lazy dog" // PAST replaceWith("My", 0, 3) // FUTURE replaceWith("fast", 4, 9) © 2017 Reginald Braithwaite. Some rights reserved. 54
buffer.redo() "My qfastbrown fox jumped over the lazy dog" //
PAST replaceWith("My", 0, 3) replaceWith("fast", 4, 9) // FUTURE © 2017 Reginald Braithwaite. Some rights reserved. 55
every command depends on the history of commands preceding it
© 2017 Reginald Braithwaite. Some rights reserved. 56
prepending a command into its history alters the command ©
2017 Reginald Braithwaite. Some rights reserved. 57
let buffer = new Buffer( "The quick brown fox jumped
over the lazy dog" ); let fast = new Edit( buffer, { replacement: "fast", from: 4, to: 9 } ); let my = new Edit( buffer, { replacement: "My", from: 0, to: 3 } ); © 2017 Reginald Braithwaite. Some rights reserved. 58
let buffer = new Buffer( "The quick brown fox jumped
over the lazy dog" ); let fast = new Edit( buffer, { replacement: "fast", from: 4, to: 9 } ); let my = new Edit( buffer, { replacement: "My", from: 0, to: 3 } ); © 2017 Reginald Braithwaite. Some rights reserved. 59
class Edit { isBefore (other) { return other.from >= this.to;
} } fast.isBefore(my); //=> false my.isBefore(fast); //=> true © 2017 Reginald Braithwaite. Some rights reserved. 60
class Edit { prependedWith (other) { if (this.isBefore(other)) { return
this; } else if (other.isBefore(this)) { let change = other.netChange(), {replacement, from, to} = this; from = from + change; to = to + change; return new Edit(this.buffer, {replacement, from, to}) } } } © 2017 Reginald Braithwaite. Some rights reserved. 61
my.prependedWith(fast) //=> buffer.replaceWith("My", 0, 3) fast.prependedWith(my) //=> buffer.replaceWith("fast", 3, 8)
© 2017 Reginald Braithwaite. Some rights reserved. 62
my.prependedWith(fast) //=> buffer.replaceWith("My", 0, 3) fast.prependedWith(my) //=> buffer.replaceWith("fast", 3, 8)
© 2017 Reginald Braithwaite. Some rights reserved. 63
class Buffer { replaceWith (replacement, from = 0, to =
this.length()) { let doer = new Edit(this, {replacement, from, to}), undoer = doer.reversed(); this.history.push(undoer); this.future = this.future.map( (edit) => edit.prependedWith(doer) ); return doer.doIt(); } } © 2017 Reginald Braithwaite. Some rights reserved. 64
let's start over © 2017 Reginald Braithwaite. Some rights reserved.
65
let buffer = new Buffer( "The quick brown fox jumped
over the lazy dog" ); buffer.replaceWith("fast", 4, 9); //=> The fast brown fox jumped over the lazy dog buffer.undo(); //=> The quick brown fox jumped over the lazy dog buffer.replaceWith("My", 0, 3); //=> My quick brown fox jumped over the lazy dog buffer.redo(); © 2017 Reginald Braithwaite. Some rights reserved. 66
"My fast brown fox jumped over the lazy dog" ©
2017 Reginald Braithwaite. Some rights reserved. 67
what did fixing redo teach us about invocations as first-
class entities? © 2017 Reginald Braithwaite. Some rights reserved. 68
"People assume that time is a strict progression of cause
to effect, but actually from a non- linear, non-subjective viewpoint— it's more like a big ball of wibbly wobbly… time-y wimey… stuff." © 2017 Reginald Braithwaite. Some rights reserved. 69
reversed() and prependedWith() show us we can change both the
direction and order of time. © 2017 Reginald Braithwaite. Some rights reserved. 70
© 2017 Reginald Braithwaite. Some rights reserved. 71
let alice = new Buffer( "The quick brown fox jumped
over the lazy dog" ); let bob = new Buffer( "The quick brown fox jumped over the lazy dog" ); © 2017 Reginald Braithwaite. Some rights reserved. 72
for simplicity, we'll omit undo , redo and reversed class
Buffer { constructor (text = '') { this.text = text; this.history = []; } } © 2017 Reginald Braithwaite. Some rights reserved. 73
class Buffer { replaceWith (replacement, from = 0, to =
this.length()) { let edit = new Edit(this, {replacement, from, to} ); this.history.push(edit); return edit.doIt(); } } © 2017 Reginald Braithwaite. Some rights reserved. 74
alice.replaceWith("My", 0, 3); //=> My quick brown fox jumped over
the lazy dog bob.replaceWith("fast", 4, 9); //=> The fast brown fox jumped over the lazy dog © 2017 Reginald Braithwaite. Some rights reserved. 75
© 2017 Reginald Braithwaite. Some rights reserved. 76
class Buffer { append (theirEdit) { this.history.forEach( (myEdit) => {
theirEdit = theirEdit.prependedWith(myEdit); }); return new Edit(this, theirEdit).doIt(); } } © 2017 Reginald Braithwaite. Some rights reserved. 77
class Buffer { appendAll(otherBuffer) { otherBuffer.history.forEach( (theirEdit) => this.append(theirEdit) );
return this; } } © 2017 Reginald Braithwaite. Some rights reserved. 78
alice.appendAll(bob); //=> My fast brown fox jumped over the lazy
dog bob.appendAll(alice); //=> My fast brown fox jumped over the lazy dog © 2017 Reginald Braithwaite. Some rights reserved. 79
! © 2017 Reginald Braithwaite. Some rights reserved. 80
© 2017 Reginald Braithwaite. Some rights reserved. 81
let GUID = () => ??? class Buffer { constructor
(text = '', history = []) { let befores = new Set(history.map(e => e.guid)); history = history.slice(0); Object.assign(this, {text, history, befores}); } share () { return new Buffer(this.text, this.history); } } © 2017 Reginald Braithwaite. Some rights reserved. 82
class Edit { constructor (buffer, { guid = GUID(), befores
= new Set(), replacement, from, to }) { this.buffer = buffer; befores = new Set(befores); Object.assign(this, {guid, replacement, from, to, befores}); } } © 2017 Reginald Braithwaite. Some rights reserved. 83
class Buffer { has (edit) { return this.befores.has(edit.guid); } perform
(edit) { if (!this.has(edit)) { this.history.push(edit); this.befores.add(edit.guid); return edit.doIt(); } } } © 2017 Reginald Braithwaite. Some rights reserved. 84
class Buffer { replaceWith (replacement, from = 0, to =
this.length()) { let befores = this.befores, let edit = new Edit(this, {replacement, from, to, befores} ); return this.perform(edit); } } © 2017 Reginald Braithwaite. Some rights reserved. 85
class Buffer { append (theirEdit) { this.history.forEach( (myEdit) => {
theirEdit = theirEdit.prependedWith(myEdit); }); return this.perform(new Edit(this, theirEdit)); } } © 2017 Reginald Braithwaite. Some rights reserved. 86
class Buffer { appendAll(otherBuffer) { otherBuffer.history.forEach( (theirEdit) => this.has(theirEdit) ||
this.append(theirEdit) ); return this; } } © 2017 Reginald Braithwaite. Some rights reserved. 87
class Edit { prependedWith (other) { if (this.isBefore(other) || this.befores.has(other.guid)
|| this.guid === other.guid) return this; let change = other.netChange(), {guid, replacement, from, to, befores} = this; from = from + change; to = to + change; befores = new Set(befores); befores.add(other.guid); return new Edit(this.buffer, {guid, replacement, from, to, befores}); } } © 2017 Reginald Braithwaite. Some rights reserved. 88
© 2017 Reginald Braithwaite. Some rights reserved. 89
let alice = new Buffer( "The quick brown fox jumped
over the lazy dog" ); let bob = alice.share(); //=> The quick brown fox jumped over the lazy dog alice.replaceWith("My", 0, 3); //=> My quick brown fox jumped over the lazy dog © 2017 Reginald Braithwaite. Some rights reserved. 90
let carol = alice.share(); //=> My quick brown fox jumped
over the lazy dog bob.replaceWith("fast", 4, 9); //=> The fast brown fox jumped over the lazy dog alice.appendAll(bob); //=> My fast brown fox jumped over the lazy dog © 2017 Reginald Braithwaite. Some rights reserved. 91
bob.appendAll(alice); //=> My fast brown fox jumped over the lazy
dog alice.replaceWith("spotted", 8, 13); //=> My fast spotted fox jumped over the lazy dog bob.appendAll(alice); //=> My fast spotted fox jumped over the lazy dog carol.appendAll(bob); //=> My fast spotted fox jumped over the lazy dog © 2017 Reginald Braithwaite. Some rights reserved. 92
"Unfortunately, implementing OT sucks. There's a million algorithms with different
tradeoffs, mostly trapped in academic papers. The algorithms are really hard and time consuming to implement correctly." © 2017 Reginald Braithwaite. Some rights reserved. 93
perhaps we should borrow a trick from react, and periodically
scan a "shadow buffer" for diffs that we exchange with collaborators… © 2017 Reginald Braithwaite. Some rights reserved. 94
differential synchronization © 2017 Reginald Braithwaite. Some rights reserved. 95
how does differential synchronization differ from the command pa!ern? ©
2017 Reginald Braithwaite. Some rights reserved. 96
this is a very big problem space © 2017 Reginald
Braithwaite. Some rights reserved. 97
© 2017 Reginald Braithwaite. Some rights reserved. 98
class Buffer { replaceWith () { ... } share ()
{ ... } append () { ... } appendAll () { ... } } © 2017 Reginald Braithwaite. Some rights reserved. 99
class Branch { commit () { ... } fork ()
{ ... } cherryPick () { ... } merge () { ... } } © 2017 Reginald Braithwaite. Some rights reserved. 100
distributed version control © 2017 Reginald Braithwaite. Some rights reserved.
101
with invocations as first-class entities, we can build distributed algorithms
and protocols; we can master time and change © 2017 Reginald Braithwaite. Some rights reserved. 102
© 2017 Reginald Braithwaite. Some rights reserved. 103
© 2017 Reginald Braithwaite. Some rights reserved. 104
down, tiger! © 2017 Reginald Braithwaite. Some rights reserved. 105
separation of concerns © 2017 Reginald Braithwaite. Some rights reserved.
106
Reg Braithwaite PagerDuty, Inc. h!ps://speakerdeck.com/raganwald/ first-class-commands-an- unexpectedly-fertile-design-pa!ern raganwald.com @raganwald ©
2017 Reginald Braithwaite. Some rights reserved. 107