Upgrade to Pro — share decks privately, control downloads, hide ads and more …

First-Class Commands, the 2017 Edition

First-Class Commands, the 2017 Edition

NDC Oslo June 15, 2017

Avatar for Reg Braithwaite

Reg Braithwaite

June 15, 2017
Tweet

More Decks by Reg Braithwaite

Other Decks in Technology

Transcript

  1. 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
  2. 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
  3. we can treat methods as first- class entities © 2017

    Reginald Braithwaite. Some rights reserved. 11
  4. what does it mean to treat an invocation as a

    first-class entity? © 2017 Reginald Braithwaite. Some rights reserved. 13
  5. 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
  6. 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
  7. 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
  8. class Edit { netChange () { return this.from - this.to

    + this.replacement.length; } } © 2017 Reginald Braithwaite. Some rights reserved. 21
  9. 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
  10. 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
  11. 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
  12. class Buffer { constructor (text = '') { this.text =

    text; this.history = []; this.future = []; } } © 2017 Reginald Braithwaite. Some rights reserved. 31
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. invocations as first-class entities: we stored them; we queried them;

    we transformed them. © 2017 Reginald Braithwaite. Some rights reserved. 41
  20. 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
  21. why do we have to throw the future away? ©

    2017 Reginald Braithwaite. Some rights reserved. 44
  22. 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
  23. 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
  24. "My qfastbrown fox jumped over the lazy dog" © 2017

    Reginald Braithwaite. Some rights reserved. 48
  25. 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
  26. 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
  27. buffer.undo() "The quick brown fox jumped over the lazy dog"

    // PAST // FUTURE replaceWith("fast", 4, 9) © 2017 Reginald Braithwaite. Some rights reserved. 53
  28. 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
  29. 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
  30. every command depends on the history of commands preceding it

    © 2017 Reginald Braithwaite. Some rights reserved. 56
  31. prepending a command into its history alters the command ©

    2017 Reginald Braithwaite. Some rights reserved. 57
  32. 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
  33. 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
  34. class Edit { isBefore (other) { return other.from >= this.to;

    } } fast.isBefore(my); //=> false my.isBefore(fast); //=> true © 2017 Reginald Braithwaite. Some rights reserved. 60
  35. 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
  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 = this.future.map( (edit) => edit.prependedWith(doer) ); return doer.doIt(); } } © 2017 Reginald Braithwaite. Some rights reserved. 64
  37. 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
  38. "My fast brown fox jumped over the lazy dog" ©

    2017 Reginald Braithwaite. Some rights reserved. 67
  39. what did fixing redo teach us about invocations as first-

    class entities? © 2017 Reginald Braithwaite. Some rights reserved. 68
  40. "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
  41. reversed() and prependedWith() show us we can change both the

    direction and order of time. © 2017 Reginald Braithwaite. Some rights reserved. 70
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. class Buffer { appendAll(otherBuffer) { otherBuffer.history.forEach( (theirEdit) => this.append(theirEdit) );

    return this; } } © 2017 Reginald Braithwaite. Some rights reserved. 78
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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
  54. class Buffer { appendAll(otherBuffer) { otherBuffer.history.forEach( (theirEdit) => this.has(theirEdit) ||

    this.append(theirEdit) ); return this; } } © 2017 Reginald Braithwaite. Some rights reserved. 87
  55. 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
  56. 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
  57. 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
  58. 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
  59. "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
  60. 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
  61. how does differential synchronization differ from the command pa!ern? ©

    2017 Reginald Braithwaite. Some rights reserved. 96
  62. this is a very big problem space © 2017 Reginald

    Braithwaite. Some rights reserved. 97
  63. class Buffer { replaceWith () { ... } share ()

    { ... } append () { ... } appendAll () { ... } } © 2017 Reginald Braithwaite. Some rights reserved. 99
  64. class Branch { commit () { ... } fork ()

    { ... } cherryPick () { ... } merge () { ... } } © 2017 Reginald Braithwaite. Some rights reserved. 100
  65. 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