Dire che ho una monade di stato, e voglio fare alcune manipolazioni sullo stato e potrei voler annullare la modifica in futuro. In generale, come posso farlo decentemente?Come posso aggiungere in modo decente una funzionalità di "annullamento" alle monadi di stato?
Per fare un esempio concreto, supponiamo che lo stato sia solo un Int
e che la manipolazione sia solo per aumentare il numero di uno.
type TestM a = StateT a IO()
inc :: TestM Int
inc = modify (+ 1)
tuttavia, se voglio tenere traccia di tutta la storia degli Stati in caso voglio disfare a uno stato precedente, il meglio che posso pensare è per avvolgere gli stati in una pila: ogni modifica lo stato verrà inserito nello stack in modo da poter annullare le modifiche mediante il rilascio dell'elemento superiore nello stack.
-- just for showing what's 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)
Come previsto, ecco l'output:
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])
Lo svantaggio del mio approccio:
tail
ehead
sono sicuri- One devono usare qualcosa come
recordDo
esplicitamente , ma immagino che questo sia inevitabile perché altrimenti ci sarà qualche problema di incoerenza. Ad esempio, l'aumento del numero di due può essere effettuato tramiteinc' >> inc'
orecordDo (inc >> inc)
e questi due approcci hanno effetti diversi sullo stack.
Quindi sono in cerca di alcuni modi per renderlo più decente o qualcosa che faccia il lavoro di "stato reversibile" meglio.
Il check-point sarebbe più gradevole? È possibile creare un nuovo tipo di monade Annullabile s m a = StatoT (Punto di controllo mappa s) (StatoT m) a' e includere funzioni di aiuto di 'mkCheckpoint :: Undoable s m Checkpoint' e' revertToCheckpoint :: Checkpoint -> Annullabile s m a'. –
Hai guardato [tardis] (https://hackage.haskell.org/package/tardis-0.3.0.0)? – bheklilr
@ ThomasM.DuBuisson sembra più potente di quello che voglio, avere la possibilità di tornare alla cronologia più recente sarà sufficiente. Forse migliorerò il mio approccio con 'safeHead' e' safeTail'. ma sembra un po 'più prolisso. – Javran