StateUndo

Jan. 16th, 2007 04:42 pm
lomeo: (лямбда)
[personal profile] lomeo
Состояние, позволяющее откатывать изменения, легко реализуется поверх уже имеющегося типа данных 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 можно делать.

Date: 2007-01-16 03:27 pm (UTC)
From: [identity profile] thesz.livejournal.com
Клёво!

Можно начинать писать Фотошоп. ;)

Date: 2007-01-16 03:57 pm (UTC)

Date: 2007-01-17 03:29 am (UTC)
From: [identity profile] kmmbvnr.livejournal.com
Задание сделать работу с history как в Emacs'e оставляем читателям

Date: 2007-01-17 08:43 am (UTC)
From: [identity profile] lomeo.livejournal.com
Особенно M-x M-r ;-)

Profile

lomeo: (Default)
Dmitry Antonyuk

April 2024

S M T W T F S
 123456
7891011 1213
14151617181920
21222324252627
282930    

Style Credit

Expand Cut Tags

No cut tags
Page generated Jul. 15th, 2025 09:02 pm
Powered by Dreamwidth Studios