2012-07-06 23 views
5

Estoy tratando de seguir los consejos dados en Combine state with IO actions para construir un AppState junto con una món IO. Lo que obtuve es esto:¿Cómo ejecuto realmente una mónada StateT junto con IO?

module Main where 

import Control.Monad.State 
import Control.Monad.Trans 

data ST = ST [Integer] deriving (Show) 
type AppState = StateT ST IO 

new = ST [] 

append :: Integer -> State ST() 
append v = state $ \(ST lst) -> ((), ST (lst ++ [v])) 

sumST :: State ST Integer 
sumST = state $ \(ST lst) -> (sum lst, ST lst) 

script = do 
    append 5 
    append 10 
    append 15 
    sumST 

myMain :: AppState() 
myMain = do 
    liftIO $ putStrLn "myMain start" 
    let (res, st) = runState script new 
    liftIO $ putStrLn $ show res 
    liftIO $ putStrLn "myMain stop" 

main = runStateT myMain (ST [15]) 

Hay una parte de esto que no obtendré. Me molesta mucho tener script y myMainymain. También me molesta que tengo que ejecutar runState dentro de myMain y que tengo que alimentar un estado inicial en runStateT en mi función principal. Estoy queriendo tener mi "script", por así decirlo, directamente en la función myMain porque todo el punto de myMain es poder ejecutar el apéndice y sumar directamente en myMain y justo al lado de las operaciones de impresión. Creo que debería ser capaz de hacer esto, en su lugar:

myMain :: AppState() 
myMain = do 
    liftIO $ putStrLn "myMain start" 
    append 5 
    append 10 
    append 15 
    r <- sumST 
    liftIO $ putStrLn $ show res 
    liftIO $ putStrLn "myMain stop" 

main = runState myMain 

había pensado que el punto del transformador mónada fue así que puede ejecutar operaciones de mi mónada de estados en una función (como arriba) y levante las operaciones de IO en esa función. ¿Cuál es la forma correcta de configurar todo esto para que pueda eliminar una de las capas de indirección?


Además de solución de Daniel (que he marcado la solución), que también han encontrado algunas variaciones que también podría arrojar alguna luz sobre la situación. En primer lugar, la aplicación definitiva de mymain y principal:

myMain :: AppState() 
myMain = do 
    liftIO $ putStrLn "myMain start" 
    append 5 
    append 10 
    append 15 
    res <- sumST 
    liftIO $ putStrLn $ show res 
    liftIO $ putStrLn "myMain stop" 

main = runStateT myMain new 

Ahora, varias implementaciones de agregación y de sumST, además de Daniel:

append :: Integer -> AppState() 
append v = state $ \(ST lst) -> ((), ST (lst ++ [v])) 

sumST :: AppState Integer 
sumST = state $ \(ST lst) -> (sum lst, ST lst) 

y (Tenga en cuenta que sólo los cambios de declaración de tipo, de hecho se puede omitir la declaración de tipo completo!)

append :: MonadState ST m => Integer -> m() 
append v = state $ \(ST lst) -> ((), ST (lst ++ [v])) 

sumST :: MonadState ST m => m Integer 
sumST = state $ \(ST lst) -> (sum lst, ST lst) 

se me ocurrió que la mónada AppState/Statet es no el mismo que el Mónada de estado básico, y estaba codificando tanto sumST como anexar para la mónada de estado. En cierto sentido, también tuvieron que ser elevados a la mónada StateT, aunque la forma correcta de pensar es que tenían que ser ejecutar en la mónada (por lo tanto, runState script new).

No estoy seguro de obtenerlo completamente, pero trabajaré con él por un tiempo, leeré el código de MonadState y escribiré algo sobre esto cuando finalmente funcione en mi cabeza.

+0

Ah, 'state' es más polimórfico de lo que había supuesto, porque tenía en mi cabeza por alguna razón que esta función era la excusa para no exportar un constructor' State'. ¡TIL! –

Respuesta

10

¡El problema es que usted hizo que sus funciones append y sumST sean demasiado monomórficas! En lugar de utilizar directamente la función state, debe utilizar los más polimórficos get y put funciones, por lo que se les puede dar a los tipos más emocionantes

append :: MonadState ST m => Integer -> m() 
append v = do 
    ST lst <- get 
    put (ST (lst ++ [v])) 

sumST :: MonadState ST m => m Integer 
sumST = do 
    ST lst <- get 
    return (sum lst) 

entonces usted puede escribir exactamente el myMain usted propuso (aunque usted todavía tiene que dar un estado inicial en main).

Como lo estilístico, me propongo no definir un nuevo tipo ST: hay un montón de funciones que hacen cosas utiles con listas, y haciendo imposible su uso mediante la imposición de un constructor ST entre usted y las listas se pueden ¡molesto!Si utiliza [Integer] como su tipo de estado en su lugar, puede hacer que las definiciones de esta manera:

prepend :: MonadState [Integer] m => Integer -> m() 
prepend = modify . (:) 

sumST :: MonadState [Integer] m => m Integer 
sumST = gets sum 

se ve bastante bien, ¿no? =)

+0

En realidad, codificar append y sumST tiene sentido si se considera una simplificación para un tipo de datos más complicado. Me gusta (en mi aplicación real) ST {datastore :: MyData, event_stream :: [Events]} –

Cuestiones relacionadas