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: 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
170
Waste in Software Development
raganwald
0
220
First-Class Commands, the 2017 Edition
raganwald
1
250
Optimism and the Growth Mindset
raganwald
0
360
ember-concurrency: an experience report
raganwald
1
170
Optimism II
raganwald
0
460
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
FactoryBotアンチパターン / factory_bot anti-patterns
toshimaru
0
140
2026-10-01_MagicPod_QAハーネスエンジニアリングとQA組織の未来像
ynisqa1988
1
550
The seven pitfalls of AI (revised version)
ufried
0
280
不可逆な意思決定を避けながら10億超の顧客データを AI Ready にするデータ基盤設計
baseballyama
1
150
[2026-09-30]ロックンロールは鳴り止まないっ - 信頼性かまってちゃん - 「データ駆動を投げ捨ててまで。」追いかける信頼性改善に向けた取り組みの話
tosite
0
210
カンファレンスに参加した後の浮遊感とセルフケア
pauli
0
310
予測不能な実行エージェントを安全にサクッと試せるサンドボックス環境の話
sansantech
PRO
0
130
DWH の限界突破! AI エージェント向け爆速リアルタイムデータ基盤 ClickHouse
jozono
1
210
ビジネスを止めない技術的負債の返済のための戦略とその手法 - 技術的負債と向き合う / Complexity and Simplicity
soudai
PRO
3
650
Google Cloud Next Tokyo 26登壇時のスクリプト
recruitengineers
PRO
0
200
LocalStack を使ったサーバーレスアプリケーション開発 / Serverless Development with LocalStack
kakakakakku
0
270
[AWS 秋のクラウドオペレーション祭り 2026]AWS DevOps Agentで変わるリリースと運用対応 ~リリース管理機能と Directed actions のご紹介~
furuton
4
1.1k
Featured
See All Featured
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
3k
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
450
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
390
The Cost Of JavaScript in 2023
addyosmani
55
10k
Design in an AI World
tapps
1
350
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
470
brightonSEO & MeasureFest 2025 - Winning Strategies for Black Friday CRO & PPC - Christian Goodrich
cargoodrich
3
860
Building a A Zero-Code AI SEO Workflow
portentint
PRO
0
760
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
2
1.6k
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
Art, The Web, and Tiny UX
lynnandtonic
304
22k
Java REST API Framework Comparison - PWX 2021
mraible
34
9.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