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
First-Class Commands: an unexpectedly fertile d...
Search
Reg Braithwaite
January 14, 2016
Technology
3k
4
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
First-Class Commands: an unexpectedly fertile design pattern
https://github.com/raganwald/presentations/blob/master/command-pattern.md
Reg Braithwaite
January 14, 2016
More Decks by Reg Braithwaite
See All by Reg Braithwaite
Courage
raganwald
0
150
Waste in Software Development
raganwald
0
220
First-Class Commands, the 2017 Edition
raganwald
1
250
Optimism and the Growth Mindset
raganwald
0
350
ember-concurrency: an experience report
raganwald
1
170
Optimism II
raganwald
0
450
Optimism
raganwald
0
2.1k
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
それでも、技術なブログを書く理由 #kichijojipm / Why I Still Write Tech Blogs Even Now
shinkufencer
0
1.6k
Webの技術とガジェットで子どもも大人も楽しめるワクワク体験を提供する / Qiita Tech Festa Day 2026
you
PRO
1
330
侵入は突然に 〜 IoTマルウェアと悪用される家庭の機器 ~ / When Intrusion Strikes: IoT Malware and the Abuse of Home Devices
nttcom
0
960
大 AI 時代におけるC# の事情 ~ぶっちゃけトークを交えながら~
nenonaninu
1
630
AIは実装を速くする。では、私たちは何を今作るべきか?-立場を越えてリリースに向き合ったチーム開発の実践 / 20260801 Hiromi Nakaya and Naoki Takahashi
shift_evolve
PRO
3
250
もう一度考える SRE チームの作り方・育て方 / Rethinking SRE #1: Building and Growing SRE Teams
rrreeeyyy
5
750
Pavlokで始める電撃駆動開発
sgrsn
0
150
Master Dataグループ紹介資料
sansan33
PRO
1
4.8k
A Bag-of-Documents Model for Query Specificity
dtunkelang
0
200
AIエージェントの知識表現と推論に なぜグラフが使われるのか - 記号的AIの復権とニューラルAIとの統合
yohei1126
1
250
【CEDEC2026】ゲームシナリオライターを支援するAIツール開発の実践 ― 設計とプロンプトの工夫 ―
cygames
PRO
1
330
ガバメントクラウドでのランサムウェア対策
techniczna
1
550
Featured
See All Featured
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
450
Making Projects Easy
brettharned
120
6.7k
Utilizing Notion as your number one productivity tool
mfonobong
4
490
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
2
430
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
520
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
2.9k
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
6k
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
360
The Pragmatic Product Professional
lauravandoore
37
7.4k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1.1k
sira's awesome portfolio website redesign presentation
elsirapls
0
310
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
2
3.7k
Transcript
© 2016 Reginald Braithwaite. Some rights reserved. 1
First-Class Commands an unexpectedly fer/le design pa3ern © 2016 Reginald
Braithwaite. Some rights reserved. 2
why do we care about commands? © 2016 Reginald Braithwaite.
Some rights reserved. 3
© 2016 Reginald Braithwaite. Some rights reserved. 4
the canonical example: mutable data © 2016 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; } } © 2016 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 © 2016 Reginald Braithwaite. Some rights reserved. 7
buffer is an object © 2016 Reginald Braithwaite. Some rights
reserved. 8
we treat objects as first-class en00es © 2016 Reginald Braithwaite.
Some rights reserved. 9
replaceWith is a method © 2016 Reginald Braithwaite. Some rights
reserved. 10
we can treat methods as first-class en11es © 2016 Reginald
Braithwaite. Some rights reserved. 11
buffer.replaceWith("fast", 4, 9) is an invoca)on © 2016 Reginald Braithwaite.
Some rights reserved. 12
what does it mean to treat an invoca0on as a
first-class en0ty? © 2016 Reginald Braithwaite. Some rights reserved. 13
store it © 2016 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; } } © 2016 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; } } © 2016 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 © 2016 Reginald Braithwaite. Some rights reserved. 17
query it © 2016 Reginald Braithwaite. Some rights reserved. 18
class Edit { netChange () { return this.from - this.to
+ this.replacement.length; } } © 2016 Reginald Braithwaite. Some rights reserved. 19
let buffer = new Buffer(); buffer.replaceWith( "The quick brown fox
jumped over the lazy dog" ).netChange(); //=> 44 buffer.replaceWith("fast", 4, 9).netChange(); //=> -1 © 2016 Reginald Braithwaite. Some rights reserved. 20
transform it © 2016 Reginald Braithwaite. Some rights reserved. 21
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}); } } © 2016 Reginald Braithwaite. Some rights reserved. 22
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 © 2016 Reginald Braithwaite. Some rights reserved. 23
all together now © 2016 Reginald Braithwaite. Some rights reserved.
24
class Buffer { constructor (text = '') { this.text =
text; this.history = []; this.future = []; } } © 2016 Reginald Braithwaite. Some rights reserved. 25
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(); } } © 2016 Reginald Braithwaite. Some rights reserved. 26
undo © 2016 Reginald Braithwaite. Some rights reserved. 27
class Buffer { undo () { let undoer = this.history.pop(),
redoer = undoer.reversed(); this.future.unshift(redoer); return undoer.doIt(); } } © 2016 Reginald Braithwaite. Some rights reserved. 28
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 © 2016 Reginald Braithwaite. Some rights reserved. 29
buffer.undo() //=> The fast brown fox jumped over the lazy
dog buffer.undo() //=> The quick brown fox jumped over the lazy dog © 2016 Reginald Braithwaite. Some rights reserved. 30
redo © 2016 Reginald Braithwaite. Some rights reserved. 31
class Buffer { redo () { let redoer = this.future.shift(),
undoer = redoer.reversed(); this.history.push(undoer); return redoer.doIt(); } } © 2016 Reginald Braithwaite. Some rights reserved. 32
buffer.redo() //=> The fast brown fox jumped over the lazy
dog buffer.redo() //=> The fast brown fox jumped over the lazy canine © 2016 Reginald Braithwaite. Some rights reserved. 33
that's the basic command pa0ern © 2016 Reginald Braithwaite. Some
rights reserved. 34
invoca'ons as first-class en''es: we stored them; we queried them;
we transformed them. © 2016 Reginald Braithwaite. Some rights reserved. 35
ques%on! © 2016 Reginald Braithwaite. Some rights reserved. 36
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(); } } © 2016 Reginald Braithwaite. Some rights reserved. 37
why do we have to throw the future away? ©
2016 Reginald Braithwaite. Some rights reserved. 38
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(); } } © 2016 Reginald Braithwaite. Some rights reserved. 39
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 © 2016 Reginald Braithwaite. Some rights reserved. 40
what happens when we evaluate buffer.redo()? © 2016 Reginald Braithwaite.
Some rights reserved. 41
"My qfastbrown fox jumped over the lazy dog" © 2016
Reginald Braithwaite. Some rights reserved. 42
© 2016 Reginald Braithwaite. Some rights reserved. 43
let's consider commands as a history © 2016 Reginald Braithwaite.
Some rights reserved. 44
let buffer = new Buffer("The quick brown fox jumped over
the lazy dog"); "The quick brown fox jumped over the lazy dog" // PAST // FUTURE © 2016 Reginald Braithwaite. Some rights reserved. 45
buffer.replaceWith("fast", 4, 9) "The fast brown fox jumped over the
lazy dog" // PAST replaceWith("fast", 4, 9) // FUTURE © 2016 Reginald Braithwaite. Some rights reserved. 46
buffer.undo() "The quick brown fox jumped over the lazy dog"
// PAST // FUTURE replaceWith("fast", 4, 9) © 2016 Reginald Braithwaite. Some rights reserved. 47
buffer.replaceWith("My", 0, 3) "My quick brown fox jumped over the
lazy dog" // PAST replaceWith("My", 0, 3) // FUTURE replaceWith("fast", 4, 9) © 2016 Reginald Braithwaite. Some rights reserved. 48
buffer.redo() "My qfastbrown fox jumped over the lazy dog" //
PAST replaceWith("My", 0, 3) replaceWith("fast", 4, 9) // FUTURE © 2016 Reginald Braithwaite. Some rights reserved. 49
every command depends on the history of commands preceding it
© 2016 Reginald Braithwaite. Some rights reserved. 50
prepending a command into its history alters the command ©
2016 Reginald Braithwaite. Some rights reserved. 51
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 } ); © 2016 Reginald Braithwaite. Some rights reserved. 52
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 } ); © 2016 Reginald Braithwaite. Some rights reserved. 53
class Edit { isBefore (other) { return other.from >= this.to;
} } fast.isBefore(my); //=> false my.isBefore(fast); //=> true © 2016 Reginald Braithwaite. Some rights reserved. 54
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}) } } } © 2016 Reginald Braithwaite. Some rights reserved. 55
my.prependedWith(fast) //=> buffer.replaceWith("My", 0, 3) fast.prependedWith(my) //=> buffer.replaceWith("fast", 3, 8)
© 2016 Reginald Braithwaite. Some rights reserved. 56
my.prependedWith(fast) //=> buffer.replaceWith("My", 0, 3) fast.prependedWith(my) //=> buffer.replaceWith("fast", 3, 8)
© 2016 Reginald Braithwaite. Some rights reserved. 57
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(); } } © 2016 Reginald Braithwaite. Some rights reserved. 58
let's start over © 2016 Reginald Braithwaite. Some rights reserved.
59
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(); © 2016 Reginald Braithwaite. Some rights reserved. 60
"My fast brown fox jumped over the lazy dog" ©
2016 Reginald Braithwaite. Some rights reserved. 61
what did fixing redo teach us about invoca3ons as first-class
en33es? © 2016 Reginald Braithwaite. Some rights reserved. 62
"People assume that .me is a strict progression of cause
to effect, but actually from a non-linear, non- subjec.ve viewpoint—it's more like a big ball of wibbly wobbly… .me-y wimey… stuff." © 2016 Reginald Braithwaite. Some rights reserved. 63
reversed() and prependedWith() show us we can change both the
direc1on and order of 1me. © 2016 Reginald Braithwaite. Some rights reserved. 64
© 2016 Reginald Braithwaite. Some rights reserved. 65
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" ); © 2016 Reginald Braithwaite. Some rights reserved. 66
for simplicity, we'll omit undo , redo and reversed class
Buffer { constructor (text = '') { this.text = text; this.history = []; } } © 2016 Reginald Braithwaite. Some rights reserved. 67
class Buffer { replaceWith (replacement, from = 0, to =
this.length()) { let edit = new Edit(this, {replacement, from, to} ); this.history.push(edit); return edit.doIt(); } } © 2016 Reginald Braithwaite. Some rights reserved. 68
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 © 2016 Reginald Braithwaite. Some rights reserved. 69
© 2016 Reginald Braithwaite. Some rights reserved. 70
class Buffer { append (theirEdit) { this.history.forEach( (myEdit) => {
theirEdit = theirEdit.prependedWith(myEdit); }); return new Edit(this, theirEdit).doIt(); } } © 2016 Reginald Braithwaite. Some rights reserved. 71
class Buffer { appendAll(otherBuffer) { otherBuffer.history.forEach( (theirEdit) => this.append(theirEdit) );
return this; } } © 2016 Reginald Braithwaite. Some rights reserved. 72
alice.appendAll(bob); //=> My fast brown fox jumped over the lazy
dog bob.appendAll(alice); //=> My fast brown fox jumped over the lazy dog © 2016 Reginald Braithwaite. Some rights reserved. 73
! © 2016 Reginald Braithwaite. Some rights reserved. 74
© 2016 Reginald Braithwaite. Some rights reserved. 75
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); } } © 2016 Reginald Braithwaite. Some rights reserved. 76
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}); } } © 2016 Reginald Braithwaite. Some rights reserved. 77
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(); } } } © 2016 Reginald Braithwaite. Some rights reserved. 78
class Buffer { replaceWith (replacement, from = 0, to =
this.length()) { let edit = new Edit(this, {replacement, from, to, befores: this.befores} ); return this.perform(edit); } } © 2016 Reginald Braithwaite. Some rights reserved. 79
class Buffer { append (theirEdit) { this.history.forEach( (myEdit) => {
theirEdit = theirEdit.prependedWith(myEdit); }); return this.perform(new Edit(this, theirEdit)); } } © 2016 Reginald Braithwaite. Some rights reserved. 80
class Buffer { appendAll(otherBuffer) { otherBuffer.history.forEach( (theirEdit) => this.has(theirEdit) ||
this.append(theirEdit) ); return this; } } © 2016 Reginald Braithwaite. Some rights reserved. 81
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}); } } © 2016 Reginald Braithwaite. Some rights reserved. 82
© 2016 Reginald Braithwaite. Some rights reserved. 83
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 © 2016 Reginald Braithwaite. Some rights reserved. 84
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 © 2016 Reginald Braithwaite. Some rights reserved. 85
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 © 2016 Reginald Braithwaite. Some rights reserved. 86
"Unfortunately, implemen2ng OT sucks. There's a million algorithms with different
tradeoffs, mostly trapped in academic papers. The algorithms are really hard and 2me consuming to implement correctly." © 2016 Reginald Braithwaite. Some rights reserved. 87
perhaps we should borrow a trick from react, and periodically
scan a "shadow buffer" for diffs that we exchange with collaborators… © 2016 Reginald Braithwaite. Some rights reserved. 88
differen'al synchroniza'on © 2016 Reginald Braithwaite. Some rights reserved. 89
this is a very big problem space © 2016 Reginald
Braithwaite. Some rights reserved. 90
© 2016 Reginald Braithwaite. Some rights reserved. 91
class Buffer { replaceWith () { ... } share ()
{ ... } append () { ... } appendAll () { ... } } © 2016 Reginald Braithwaite. Some rights reserved. 92
class Branch { commit () { ... } fork ()
{ ... } cherryPick () { ... } merge () { ... } } © 2016 Reginald Braithwaite. Some rights reserved. 93
distributed version control © 2016 Reginald Braithwaite. Some rights reserved.
94
with invoca+ons as first-class en++es, we can build algorithms and
protocols mastering +me and change, from single apps to distributed systems © 2016 Reginald Braithwaite. Some rights reserved. 95
© 2016 Reginald Braithwaite. Some rights reserved. 96
"Never confuse the example given of a pa6ern, with the
underlying idea the pa6ern represents." © 2016 Reginald Braithwaite. Some rights reserved. 97
Reg Braithwaite PagerDuty, Inc. raganwald.com @raganwald © 2016 Reginald Braithwaite.
Some rights reserved. 98