lomeo: (лямбда)
Dmitry Antonyuk ([personal profile] lomeo) wrote2007-01-16 04:42 pm

StateUndo

Состояние, позволяющее откатывать изменения, легко реализуется поверх уже имеющегося типа данных 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 можно делать.

Post a comment in response:

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting