Скажите, что у меня есть государственная монада, и я хочу сделать некоторые манипуляции в государстве и, возможно, захочу отменить изменения в будущем. Как вообще я могу сделать это прилично?
Чтобы дать конкретный пример, допустим, что состояние - это просто Int
, а манипуляция
просто увеличить число на единицу.
type TestM a = StateT a IO ()
inc :: TestM Int
inc = modify (+ 1)
однако, если я хочу отслеживать всю историю состояний в случае, если я хочу отменить какое-то предыдущее состояние, самое лучшее, что я могу придумать, это обернуть состояния в стек: каждая модификация состояния будет толкается в стек, так что я могу отменить изменения, пробив верхний элемент в стеке.
-- just for showing what going on
traceState :: (MonadIO m, MonadState s m, Show s) => m a -> m a
traceState m = get >>= liftIO . print >> m
recordDo :: TestM a -> TestM [a]
recordDo m = do
x <- gets head
y <- liftIO $ execStateT m x
modify (y:)
inc' :: TestM [Int]
inc' = recordDo inc
undo' :: TestM [Int]
undo' = modify tail
-- inc 5 times, undo, and redo inc
manip' :: TestM [Int]
manip' = mapM_ traceState (replicate 5 inc' ++ [undo',inc'])
main :: IO ()
main = do
v1 <- execStateT (replicateM_ 5 (traceState inc)) 2
v2 <- execStateT (replicateM_ 5 (traceState inc')) [2]
v3 <- execStateT manip' [2]
print (v1,v2,v3)
Как и ожидалось, вот результат:
2
3
4
5
6
[2]
[3,2]
[4,3,2]
[5,4,3,2]
[6,5,4,3,2]
[2]
[3,2]
[4,3,2]
[5,4,3,2]
[6,5,4,3,2]
[7,6,5,4,3,2]
[6,5,4,3,2]
(7,[7,6,5,4,3,2],[7,6,5,4,3,2])
Недостаток моего подхода:
-
tail
иhead
являются небезопасными - Мне нужно явно использовать что-то вроде
recordDo
, но я думаю, это неизбежно, потому что в противном случае будет какая-то проблема несогласованности. Например, увеличение числа на два может выполняться либо с помощьюinc' >> inc'
, либоrecordDo (inc >> inc)
, и эти два подхода оказывают различное влияние на стек.
Итак, я ищу либо некоторые способы сделать его более приличным, либо то, что делает работу "обратимого состояния" лучше.