I've heard that one of the benefits of purely functional data structures is that you get undo/redo operations for free. Can someone explain why? I don't see why adding undo/redo is easier in a functional language.
For example, suppose I have the following implementation of a queue:
data Queue a = Queue [a] [a]
newQueue :: Queue a
newQueue = Queue [] []
empty :: Queue a -> Bool
empty (Queue [] []) = True
empty _ = False
enqueue :: Queue a -> a -> Queue a
enqueue (Queue xs ys) y = Queue xs (y:ys)
dequeue :: Queue a -> (a, Queue a)
dequeue (Queue [] []) = error "Queue is empty!"
dequeue (Queue [] ys) = dequeue (Queue (reverse ys) [])
dequeue (Queue (x:xs) ys) = (x, Queue xs ys)
How would I modify this to get undo and redo operations? (I could imagine that the enqueue and dequeue functions also return two lists, one of the lists being all the previous versions of the queue and the other list being all the future versions of the queue, and these lists act as our undo/redo operations, but I'm guessing this isn't what people typically have in mind.)
Which data structure is used in redo-undo feature? Explanation: Stack data structure is most suitable to implement redo-undo feature.
To implement Undo , you work backward from the tail of the linked-list, using a 'current-node' pointer or index: where the change was insert , you do a delete but without updating the linked-list; and where it was a delete you insert the data from the data in the linked-list buffer.
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. If “READ” string is encountered, print all the elements of the Undo stack in reverse order.
The Undo command allows you to discard the most recent change in Quantum Visualizer. The Redo command reverses the most recent change made using Undo. To undo an action, on the Edit menu, click Undo, or press Ctrl + Z. To redo an action, on the Edit menu, click Redo, or press Ctrl +Y.
Example:
q1 = newQueue
q2 = enque q1 3
then q1
is the undo of q2
, since all values are immutable. Just keep a reference to the prior value.
The reason undo/redo is easier to implement in purely functional code is because of two guarantees:
You can always revert back to an old data structure as long as you maintain a reference to it. If you want to store the entire history, you could keep it in a list:
trackHistory :: Queue a -> [Queue a] -> [Queue a]
trackHistory currentQueue history = currentQueue : history
Obviously in a real application you would want to cap the history size, but you get the idea. This works because you can guarantee that each of the queues in your list hasn't been changed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With