2012-09-08 20 views
8

cuando pasé por el último capítulo de Lyah y se reunió con ListZipper, me di a una asignación que para que sea una mónada Estado para que el código fuente se vería más claro como:¿cómo podría aprovechar el estado y el escritor en haskell?

manipList = do 
    goForward 
    goForward 
    goBack 

y, al mismo tiempo, quería mantener un registro para este proceso aprovechando la mónada Writer, pero no sabía cómo combinar estas dos Mónadas.

Mi solución era mantener un [cadena] en el interior del estado, y el código fuente es

import Control.Monad 
import Control.Monad.State 

type ListZipper a = ([a], [a]) 

-- move focus forward, put previous root into breadcrumbs 
goForward :: ListZipper a -> ListZipper a 
goForward (x:xs, bs) = (xs, x:bs) 

-- move focus back, restore previous root from breadcrumbs 
goBack :: ListZipper a -> ListZipper a 
goBack (xs, b:bs) = (b:xs, bs) 

-- wrap goForward so it becomes a State 
goForwardM :: State (ListZipper a) [a] 
goForwardM = state stateTrans where 
    stateTrans z = (fst newZ, newZ) where 
     newZ = goForward z 

-- wrap goBack so it becomes a State 
goBackM :: State (ListZipper a) [a] 
goBackM = state stateTrans where 
    stateTrans z = (fst newZ, newZ) where 
     newZ = goBack z 

-- here I have tried to combine State with something like a Writer 
-- so that I kept an extra [String] and add logs to it manually 

-- nothing but write out current focus 
printLog :: Show a => State (ListZipper a, [String]) [a] 
printLog = state $ \(z, logs) -> (fst z, (z, ("print current focus: " ++ (show $ fst z)):logs)) 

-- wrap goForward and record this move 
goForwardLog :: Show a => State (ListZipper a, [String]) [a] 
goForwardLog = state stateTrans where 
    stateTrans (z, logs) = (fst newZ, (newZ, newLog:logs)) where 
     newZ = goForward z 
     newLog = "go forward, current focus: " ++ (show $ fst newZ) 

-- wrap goBack and record this move 
goBackLog :: Show a => State (ListZipper a, [String]) [a] 
goBackLog = state stateTrans where 
    stateTrans (z, logs) = (fst newZ, (newZ, newLog:logs)) where 
     newZ = goBack z 
     newLog = "go back, current focus: " ++ (show $ fst newZ) 

-- return 
listZipper :: [a] -> ListZipper a 
listZipper xs = (xs, []) 

-- return 
stateZipper :: [a] -> (ListZipper a, [String]) 
stateZipper xs = (listZipper xs, []) 

_performTestCase1 = do 
    goForwardM 
    goForwardM 
    goBackM 

performTestCase1 = 
    putStrLn $ show $ runState _performTestCase1 (listZipper [1..4]) 

_performTestCase2 = do 
    printLog 
    goForwardLog 
    goForwardLog 
    goBackLog 
    printLog 

performTestCase2 = do 
    let (result2, (zipper2, log2)) = runState _performTestCase2 $ stateZipper [1..4] 
    putStrLn $ "Result: " ++ (show result2) 
    putStrLn $ "Zipper: " ++ (show zipper2) 
    putStrLn "Logs are: " 
    mapM_ putStrLn (reverse log2) 

Pero el problema es que no creo que esto es una buena solución ya que tengo que mantener mi registros manualmente ¿Hay alguna forma alternativa de mezclar Mónada de Estado y Mónada de Escritores para que puedan trabajar juntos?

Respuesta

16

Usted está buscando monad transformers. La idea básica es definir un tipo como WriterT que toma otra mónada y la combina con un Writer creando un nuevo tipo (como WriterT log (State s)).

Nota: hay una convención que los tipos de transformadores terminan con un capital T. Por lo tanto, Maybe y Writer son mónadas normales y MaybeT y WriterT son sus equivalentes de transformador.

La idea central es muy simple: para un grupo de mónadas, es fácil imaginar la combinación de su comportamiento en aprieto. El ejemplo más simple es Maybe. Recordemos que todo lo que hace es propagar MaybeNothing en aprieto:

Nothing >>= f = Nothing 
Just x >>= f = f x 

lo que debe ser fácil imaginar que se extiende cualquier mónada con este comportamiento. Todo lo que hacemos es verificar primero el Nothing y luego usar el enlace de la mónada anterior. El tipo MaybeT hace exactamente esto: envuelve una mónada existente y los prefacios se unen con un cheque como este. También tendría que implementar return esencialmente envolviendo el valor en un Just y luego usando el return interno de la mónada. También hay un poco más de plomería para que todo funcione, pero esta es la idea importante.

Usted puede imaginar un comportamiento muy similar para Writer: primero combinamos cualquier nueva salida entonces usamos bind del viejo mónada. Este es esencialmente el comportamiento de WriterT. Hay otros detalles involucrados, pero la idea básica es bastante simple y útil.

transformadores Monad son una forma muy común de "combinar" mónadas como usted desee. Hay versiones de las mónadas más comúnmente usadas como transformadores, con la notable excepción de IO que siempre tiene que estar en la base de su pila de mónadas. En su caso, existen tanto WriterT como StateT y podrían usarse para su programa.

+0

¡eso es exactamente lo que quiero!Lo he intentado y ambos (WriterT & StateT) funcionan bien, ¡sí! – Javran

12

Tikhon Jelvis da una respuesta agradable con transformadores mónada. Sin embargo, también hay una solución rápida.

El módulo Control.Monad.RWS en mtl exporta el mónada RWS, que es una combinación de los Reader, Writer y State mónadas.

Cuestiones relacionadas