footer: © 2016 Reginald Braithwaite. Some rights reserved. slidenumbers: true autoscale: true
^ https://www.flickr.com/photos/fatedenied/7335413942
^ The Command Pattern
^ https://www.flickr.com/photos/fatedenied/7335413942
why do we care about commands?
^ https://www.flickr.com/photos/fatedenied/7335413942
^ Let's get started
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; }
}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^ https://www.flickr.com/photos/mwichary/2406482529
^ https://www.flickr.com/photos/tompagenet/8580371564
^ e.g. decorators
^ https://www.flickr.com/photos/ooocha/2869485136
^ Talk about invocations being ephemeral and tied up with mutable state, some visible, some hidden.
^ https://www.flickr.com/photos/oskay/2550938136
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;
}
}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; }
}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^ Presto, a job queue!
^ https://www.flickr.com/photos/baccharus/4474584940
class Edit {
netChange () {
return this.from - this.to + this.replacement.length;
}
}let buffer = new Buffer();
buffer.replaceWith(
"The quick brown fox jumped over the lazy dog"
).netChange();
//=> 44
buffer.replaceWith("fast", 4, 9).netChange();
//=> -1^ https://www.flickr.com/photos/micurs/4906349993
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});
}
}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^ https://www.flickr.com/photos/purdman1/2875431305
class Buffer {
constructor (text = '') {
this.text = text;
this.history = [];
this.future = [];
}
}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();
}
}^ https://www.flickr.com/photos/daryl_mitchell/15427050433
class Buffer {
undo () {
let undoer = this.history.pop(),
redoer = undoer.reversed();
this.future.unshift(redoer);
return undoer.doIt();
}
}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 caninebuffer.undo()
//=> The fast brown fox jumped over the lazy dog
buffer.undo()
//=> The quick brown fox jumped over the lazy dog^ https://www.flickr.com/photos/the00rig/3753005997
class Buffer {
redo () {
let redoer = this.future.shift(),
undoer = redoer.reversed();
this.history.push(undoer);
return redoer.doIt();
}
}buffer.redo()
//=> The fast brown fox jumped over the lazy dog
buffer.redo()
//=> The fast brown fox jumped over the lazy canine^ https://www.flickr.com/photos/robbie1/8656027235
^ https://www.flickr.com/photos/mwichary/2406489333
^ https://www.flickr.com/photos/pedrosimoes7/17386505158
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();
}
}^ https://www.flickr.com/photos/a-barth/2846621384
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();
}
}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^ https://www.flickr.com/photos/mleung311/9468927282
^ https://www.flickr.com/photos/bludgeoner86/5590795033
^ because our commands are coupled to ephemeral state, changing the state breaks the command
^ https://www.flickr.com/photos/49024304@N00/
"The quick brown fox jumped over the lazy dog"
// PAST
// FUTURE"The fast brown fox jumped over the lazy dog"
// PAST
replaceWith("fast", 4, 9)
// FUTURE"The quick brown fox jumped over the lazy dog"
// PAST
// FUTURE
replaceWith("fast", 4, 9)"My quick brown fox jumped over the lazy dog"
// PAST
replaceWith("My", 0, 3)
// FUTURE
replaceWith("fast", 4, 9)"My qfastbrown fox jumped over the lazy dog"
// PAST
replaceWith("My", 0, 3)
replaceWith("fast", 4, 9)
// FUTURE^ https://www.flickr.com/photos/29143375@N05/4575806708
^ https://www.flickr.com/photos/30239838@N04/4268147953
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 }
);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 }
);class Edit {
isBefore (other) {
return other.from >= this.to;
}
}
fast.isBefore(my);
//=> false
my.isBefore(fast);
//=> trueclass 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})
}
}
}my.prependedWith(fast)
//=> buffer.replaceWith("My", 0, 3)
fast.prependedWith(my)
//=> buffer.replaceWith("fast", 3, 8)my.prependedWith(fast)
//=> buffer.replaceWith("My", 0, 3)
fast.prependedWith(my)
//=> buffer.replaceWith("fast", 3, 8)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();
}
}^ https://www.flickr.com/photos/benetd/4429314827
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();^ https://www.flickr.com/photos/katiethebeau/16670836007
"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."
^ https://www.flickr.com/photos/shimgray/2811100997
^ https://www.youtube.com/watch?v=mDsN5lWLKU0
^ "Alice B. Toklas and Bob Fosse are editing a script"
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"
);class Buffer {
constructor (text = '') {
this.text = text;
this.history = [];
}
}class Buffer {
replaceWith (replacement, from = 0, to = this.length()) {
let edit = new Edit(this,
{replacement, from, to}
);
this.history.push(edit);
return edit.doIt();
}
}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 dogclass Buffer {
append (theirEdit) {
this.history.forEach( (myEdit) => {
theirEdit = theirEdit.prependedWith(myEdit);
});
return new Edit(this, theirEdit).doIt();
}
}class Buffer {
appendAll(otherBuffer) {
otherBuffer.history.forEach(
(theirEdit) => this.append(theirEdit)
);
return this;
}
}alice.appendAll(bob);
//=> My fast brown fox jumped over the lazy dog
bob.appendAll(alice);
//=> My fast brown fox jumped over the lazy dog^ "We have a little bug."
^ "Okay, a big bug! We can't appendAll more than once. Shared mutable data everywhere."
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);
}
}^ We're going to use guids and a set of before guids in the buffer and the edits
^ let GUID = () => { ^ let _p8 = (s) => { ^ let p = (Math.random().toString(16)+"000000000").substr(2,8); ^ ^ return s ? "-" + p.substr(0,4) + "-" + p.substr(4,4) : p ; ^ } ^ return _p8() + _p8(true) + _p8(true) + _p8(); ^ }
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});
}
}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();
}
}
}^ now we check edits before we perform them
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);
}
}^ simplifies replaceWith, append, and appendAll
class Buffer {
append (theirEdit) {
this.history.forEach( (myEdit) => {
theirEdit = theirEdit.prependedWith(myEdit);
});
return this.perform(new Edit(this, theirEdit));
}
}class Buffer {
appendAll(otherBuffer) {
otherBuffer.history.forEach(
(theirEdit) =>
this.has(theirEdit) || this.append(theirEdit)
);
return this;
}
}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});
}
}^ "Alice, Bob, and Carol are editing a script"
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 doglet 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 dogbob.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"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."
^ https://www.flickr.com/photos/wordridden/4308645407
^ Joseph Gentle, from https://en.wikipedia.org/wiki/Operational_transformation
^ too much responsibility in edits, and we have an XY problem
perhaps we should borrow a trick from react, and periodically scan a "shadow buffer" for diffs that we exchange with collaborators…
^ congratulations, now we're talking differential synchronization. See Electron, Google Docs
^ See also: https://neil.fraser.name/writing/sync/
^ https://www.flickr.com/photos/sidelong/18620995913
^ There are only two hard problems in computer science: Cache invalidation, and naming things--Phil Karleton
class Buffer {
replaceWith () { ... }
share () { ... }
append () { ... }
appendAll () { ... }
}class Branch {
commit () { ... }
fork () { ... }
cherryPick () { ... }
merge () { ... }
}with invocations as first-class entities, we can build distributed algorithms and protocols; we can master time and change
^ https://www.flickr.com/photos/stawarz/3848824508
"Never confuse the example given of a pattern, with the underlying idea the pattern represents."
^ https://www.flickr.com/photos/mwichary/3338901313




































