2012-09-02 10 views
5
class Monad m => MonadState s m | m -> s where 
    -- | Return the state from the internals of the monad. 
    get :: m s 
    get = state (\s -> (s, s)) 

    -- | Replace the state inside the monad. 
    put :: s -> m() 
    put s = state (\_ -> ((), s)) 

    -- | Embed a simple state action into the monad. 
    state :: (s -> (a, s)) -> m a 
    state f = do 
     s <- get 
     let ~(a, s') = f s 
     put s' 
     return a 

instance MonadState s m => MonadState s (MaybeT m) where... 

¿Por qué una instancia de MonadState necesita un estado y una mónada? ¿Por qué no crear un solo parámetro State class?Por qué usar MultiParamTypeClasses en un MonadState

+4

No estoy seguro de entender la alternativa que está sugiriendo. ¿Cómo escribirías el tipo de 'state :: (s -> (a, s)) -> m a' sin ambos' m' y 's'? – Owen

+0

digamos que comenzaríamos desde 'clase MonadState s donde ...' y simplemente hacemos 'get :: s' y' put :: s ->() 'sin poner s en una mónada? ¿Podría lograr una implementación estatal más simple en la que no tengamos que preocuparnos si se trata de un estado Tal vez o un estado O bien un estado IO? –

Respuesta

6

Déjame intentar y responder la pregunta de Gert en los comentarios, porque es una pregunta bastante diferente.

La pregunta es, ¿por qué no podemos simplemente escribir

class State s where 
    get :: s 
    put :: s ->() 

Bueno, podríamos escribir esto. Pero ahora la pregunta es, ¿qué podemos hacer con eso? Y la parte difícil es, si tenemos algún código con put x y luego get, ¿cómo enlazamos el get con el put para que se devuelva el mismo valor que el ingresado?

Y el problema es que, con solo los tipos () y s, no hay forma de vincular uno con el otro. Puede intentar implementarlo de varias maneras, pero no funcionará. Simplemente no hay forma de llevar los datos del put al get (tal vez alguien pueda explicar esto mejor, pero la mejor manera de entenderlo es intentar escribirlo).

una mónada no es necesariamente la única manera de hacer las operaciones enlazable, pero es una manera, ya que tiene el operador >> para unir dos estados juntos:

(>>) :: m a -> m b -> m b 

por lo que podemos escribir

(put x) >> get 

EDIT: Aquí está un ejemplo utilizando the StateT instance defined in the package

foo :: StateT Int IO() 
foo = do 
    put 3 
    x <- get 
    lift $ print x 

main = evalStateT foo 0 
+0

¿puedes agregar un ejemplo de IO a tu respuesta? Me gusta '(put x) >> get >> = (\ x -> print x)' o algo así? –

+1

@GertCuykens No puedo pensar cómo hacerlo con 'IO'; 'IO' no tiene un lugar para poner estado como' State'. Pero puedo agregar un ejemplo usando 'StateT' alrededor de' IO'. – Owen

+0

@Owen - puede hacer un ejemplo usando solo 'IO' con' IORef'. –

4

Necesita alguna forma de asociar el tipo de estado al tipo de mónada. MultiParamTypeClasses con FunctionalDependencies es de una manera. Sin embargo, también puede hacerlo usando TypeFamilies.

class (Monad m) => MonadState m where 
    type StateType m 

    -- | Return the state from the internals of the monad. 
    get :: m (StateType m) 
    get = state (\s -> (s, s)) 

    -- | Replace the state inside the monad. 
    put :: StateType m -> m() 
    put s = state (\_ -> ((), s)) 

    -- | Embed a simple state action into the monad. 
    state :: (StateType m -> (a, StateType m)) -> m a 
    state f = do 
     s <- get 
     let ~(a, s') = f s 
     put s' 
     return a 

instance MonadState m => MonadState (MaybeT m) where 
    type StateType (MaybeT m) = StateType m 

    ... 

Este es el enfoque adoptado por el monads-tf package.

+1

Nota: 'monads-tf' básicamente se retiró. Originalmente, Ross dividió 'mtl' en' transformers' que era Haskell 98 y dos paquetes 'monads-tf' y' monads-fd' para que las personas pudieran elegir el estilo que prefirieran. Sin embargo, esto fue terrible ya que dividió a la comunidad de 3 formas, ya que 'mtl',' monads-tf' y 'monads-fd' ¡todos usaban los mismos nombres de módulo! La solución fue retirar el viejo 'mtl', y hacer' mónadas-fd' en 'mtl' y dejar que' mónadas-tf' muera. No me opondría a un paquete con las clases de 'monads-tf' que estaba a un lado usando diferentes nombres de módulo pero, tal como está, es francamente perjudicial. –