A discussion at work took place about design patterns, and it prompted me to go back and look at something. One of the behavioural patterns is Memento, which (from Wikipedia) "provides the ability to restore an object to its previous state (undo)". The implementation described uses three objects: a caretaker, an originator and a memento object that the caretaker can use to restore the originator to a previous state.
From a functional programming viewpoint, I find this overly complex. Consider a system in which an object follows the functional principle of immutability. To "change state", a caller calls a method on that object, but the original object is unchanged: the method returns a new object with a different state. There's then no need to have the complexity of undo - the previous state is available via the original instance.
Of course, this is already the case with immutable strings in many languages. Consider this C#:
string s1="This String is Mixed Case";
//Oops - we need to undo the ToLower() operation.
s2=s1; //This is effectively an "undo"
The only principle to keep in mind is to save a reference to a previous version of the object if there is a potential need to undo it.
Benefits: huge saving of complexity, and therefore of testing. I find that the testing load is often forgotten when developers assess how much work is involved in an implementation, unless TDD is in the blood of the team. Downside: potential extra use of memory to keep hold of previous objects. With intelligent sharing of expensive resources between objects, this can be minimal.