2012-01-29 20 views
45

Tengo un problema que se ajusta muy bien con una pila de MT (o incluso una MT) sobre IO. ¡Todo está bien, excepto que usar el ascensor antes de cada acción es terriblemente molesto! Sospecho que realmente no hay nada que hacer al respecto, pero pensé que podría preguntar de todos modos.Evitar el levantamiento con Monad Transformers

Soy consciente de levantar bloques enteros, pero ¿y si el código es realmente de tipos mixtos? ¿No sería bueno si GHC arroja algo de azúcar sintáctica (por ejemplo, <-$ = <- lift)?

Respuesta

51

Para todas las mónadas estándar mtl, no necesita lift en absoluto. get, put, ask, tell - todos funcionan en cualquier mónada con el transformador correcto en algún lugar de la pila. La pieza faltante es IO, e incluso allí liftIO levanta una acción IO arbitraria por un número arbitrario de capas.

Esto se hace con las clases de tipos para cada "efecto" que se ofrece: por ejemplo, MonadState proporciona get y put. Si desea crear su propia newtype envoltorio alrededor de una pila de transformador, que puede hacer deriving (..., MonadState MyState, ...) con la extensión GeneralizedNewtypeDeriving, o rodar su propia instancia:

instance MonadState MyState MyMonad where 
    get = MyMonad get 
    put s = MyMonad (put s) 

Usted puede usar esto para exponer selectivamente u ocultar los componentes de su transformador combinado , al definir algunas instancias y no otras.

(Puede ampliar fácilmente este enfoque a los nuevos efectos monádicos que defina, definiendo su propia clase de tipos y proporcionando instancias repetitivas para los transformadores estándar, pero las mónadas nuevas son raras; la mayoría de las veces, usted Obtendré simplemente componiendo el conjunto estándar ofrecido por mtl.)

+0

Oh, creo que me siento estúpido, mencionaste que en una de tus respuestas anteriores, no podía entenderlo en ese momento. Ahora, ¡gracias! – aelguindy

43

Puede hacer que sus funciones sean monad-agnósticas mediante el uso de clases de tipos en lugar de pilas de mónadas concretas.

Digamos que usted tiene esta función, por ejemplo:

bangMe :: State String() 
bangMe = do 
    str <- get 
    put $ str ++ "!" 
    -- or just modify (++"!") 

Por supuesto, se da cuenta de que funciona como un transformador, así que se podría escribir:

bangMe :: Monad m => StateT String m() 

Sin embargo, si tienes una función que usa una pila diferente, digamos ReaderT [String] (StateT String IO)() o lo que sea, tendrás que usar la temida función lift! Entonces, ¿cómo se evita eso?

El truco es hacer que la firma de la función sea aún más genérica, por lo que dice que la mónada State puede aparecer en cualquier lugar de la pila de mónadas. Esto se hace así:

bangMe :: MonadState String m => m() 

Esto obliga m ser una mónada que soporta estado (casi) cualquier lugar en la pila mónada, y la función de este modo trabajar sin levantar para cualquier pila.

Sin embargo, hay un problema; dado que IO no es parte del mtl, no tiene un transformador (por ejemplo, IOT) ni una clase de tipo útil por defecto. Entonces, ¿qué debe hacer cuando quiere levantar acciones de IO arbitrariamente?

¡Al rescate viene MonadIO!Se comporta casi de manera idéntica a MonadState, MonadReader etc., la única diferencia es que tiene un mecanismo de elevación ligeramente diferente. Funciona de esta manera: puede tomar cualquier acción IO y usar liftIO para convertirla en una versión independiente de mónada. Por lo tanto:

action :: IO() 
liftIO action :: MonadIO m => m() 

Mediante la transformación de todas las acciones monádicos que desea utilizar de esta manera, se puede entrelazar mónadas tanto como usted desee sin ningún trabajo tedioso.

+0

¡Gracias por la respuesta detallada! Batido en el tiempo por un tercero;) – aelguindy

+4

Yo y yo proporcionamos soluciones algo diferentes a este problema. Puede valer la pena leer las dos respuestas para comprender las alternativas que tienes :) – dflemstr

+1

Es lamentable tanto es necesario. – rightfold

Cuestiones relacionadas