Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Undo/Redo with immutable objects

I read the following in an article

Immutable objects are particularly handy for implementing certain common idioms such as undo/redo and abortable transactions. Take undo for example. A common technique for implementing undo is to keep a stack of objects that somehow know how to run each command in reverse (the so-called "Command Pattern"). However, figuring out how to run a command in reverse can be tricky. A simpler technique is to maintain a stack of immutable objects representing the state of the system between successive commands. Then, to undo a command, you simply revert back to the previous system state (and probably store the current state on the redo stack).

However, the article does not show a good practical example of how immutable objects could be used to implement "undo" operations. For example... deleting 10 emails from a gmail inbox. Once you do that, it has an undo option. How would an immutable object help in this regard?

like image 880
Omnipresent Avatar asked Nov 28 '09 16:11

Omnipresent


People also ask

How do you implement undo redo?

If “UNDO” string is encountered, pop the top element from Undo stack and push it to Redo stack. If “REDO” string is encountered, pop the top element of Redo stack and push it into the Undo stack.

Can immutable objects be modified?

An immutable object cannot be modified after it was created.

How do you undo a function in Javascript?

Before an action is applied, you take a snapshot of the current state and save it into an array. That snapshot is the Memento. If the user wants to undo, you simply pop the last memento and apply it. The program returns to the state it was before the last action was applied.


1 Answers

The immutable objects would hold the entire state of the system, so in this case you'd have object A that contains the original inbox, and then object B that contains the inbox with ten e-mails deleted, and (in effect) a pointer back from B to A indicating that, if you do one "undo", then you stop using B as the state of the system and start using A instead.

However, Gmail inboxes are far too large to use this technique. You'd use it on documents that can actually be stored in a fairly small amount of memory, so that you can keep many of them around for multi-level undo.

If you want to keep ten levels of undo, you can potentially save memory by only keeping two immutable objects - one that is current, and one that is from ten "undos" ago - and a list of Commands that were applied between them.

To do an "undo", you re-execute all but the last Command object, use that as the new current object, and erase the last Command (or save it as a "Redo" object). Every time you do a new action, you update the current object, add the associated Command to the list, and then (if the list is more than ten Commands long) you execute the first Command on the object from the start of the undo list and throw away the first Command on the list.

You can do various other checkpointing systems as well, involving a variable number of complete representations of the system as well as a variable number of Commands between them. But it gets further and further from the original idea that you cited and becomes more and more like a typical mutable system. It does, however, avoid the problem of making Commands consistently reversible; you need only ever apply Commands to an object forward and not reverse.

SVN and other version control systems are effectively a disk- or network-based form of undo-and-redo.

like image 77
jprete Avatar answered Sep 23 '22 05:09

jprete