StateUndo

Jan 16, 2007 16:42

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

монады, haskell, программирование, структуры данных

Previous post Next post
Up