Состояние, позволяющее откатывать изменения, легко реализуется поверх уже имеющегося типа данных Control.Monad.State.State.
Историю состояний проще всего хранить в списке, т.к. тогда легко откатиться к предыдущему состоянию, просто взяв tail. Текущим состоянием будет голова этого списка.
> newtype StateUndo s a = StateUndo (State [s] a)
> deriving (Functor, Monad)
newtype-deriving позволяет не переписывать определение экземпляров классов Functor и Monad для нашего типа данных. Однако, чтобы StateUndo вёл себя как состояние, необходимо инстанциировать класс MonadState:
> instance MonadState s (StateUndo s) where
> get = StateUndo (gets head)
> put s = StateUndo (modify (s:))
Для того, чтобы можно было откатиться к предыдущему состоянию, нам необходима функция undo :: StateUndo a (), которая изменит историю состояний, убрав у неё голову - таким образом, текущим состоянием станет предыдущее (т.е. мы откатимся к предыдущему состоянию).
> undo :: StateUndo a ()
> undo = StateUndo (modify tail)
Несколько полезных функций для работы с монадами. Во-первых, аналоги evalState, execState. В них просто запускаем вложенное состояние, учитывая, что исходное состояние будет выглядеть как список из одного элемента - этого исходного состояния. Для execState также учтём, что текущее состояние у нас - это голова списка.
> evalStateUndo :: StateUndo s a -> s -> a
> evalStateUndo (StateUndo m) s = evalState m [s]
> execStateUndo :: StateUndo s a -> s -> s
> execStateUndo (StateUndo m) s = head (execState m [s])
Возможно, нам может понадобится история состояний. Для этого используем функцию history :: StateUndo s a -> [s], которая из монады состояния будет вытягивать её историю.
> history :: StateUndo s a -> [s]
> history (StateUndo m) = execState m []
Ну, и напоследок, функция для запуска монады без исходного состояния.
> runStateUndo :: StateUndo s a -> a
> runStateUndo (StateUndo m) = evalState m []
А вот и пример!
> test = do
> let inc = modify (1+)
> put 0
> inc
> inc
> inc
> undo
> get
Запускаем:
*Main> runStateUndo test
2
Позже обнаружил вот это -
http://haskell.org/haskellwiki/New_monads/MonadUndoТам ещё и redo можно делать.