mónada monadas leibniz las filosofia educatina doctrina haskell monads monad-transformers

haskell - monadas - Evitar el levantamiento con transformadores de mónada



monadas filosofia (2)

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

Esto se hace con clases de tipos para cada "efecto" en oferta: por ejemplo, MonadState proporciona get y put . Si desea crear su propia envoltura de tipo nuevo alrededor de una pila de transformadores, puede deriving (..., MonadState MyState, ...) con la extensión GeneralizedNewtypeDeriving , o lanzar su propia instancia:

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

Puede usar esto para exponer u ocultar componentes selectivamente de su transformador combinado, definiendo algunas instancias y otras no.

(Puede extender fácilmente este enfoque a los nuevos efectos monádicos que defina a sí mismo, 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, las obtendrá simplemente componiendo el conjunto estándar ofrecido por mtl.)

Tengo un problema para el cual una pila de transformadores de mónada (o incluso un transformador de mónada) 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.

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


Puede hacer que sus funciones sean independientes de la mónada utilizando clases de tipos en lugar de pilas de mónadas concretas.

Digamos que tienes esta función, por ejemplo:

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

Por supuesto, te das cuenta de que también funciona como un transformador, por lo que uno podría escribir:

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

Sin embargo, si tiene una función que usa una pila diferente, digamos ReaderT [String] (StateT String IO) () o lo que sea, ¡tendrá que usar la temida función de 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 de State puede aparecer en cualquier lugar de la pila de mónadas. Esto se hace así:

bangMe :: MonadState String m => m ()

Esto fuerza a m a ser una mónada que soporte el estado (virtualmente) en cualquier parte de la pila de mónadas, y la función funcionará sin levantar para tal pila.

Sin embargo, hay un problema; como 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 , MonadState , 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 liftIO agnostica. Asi que:

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

Al transformar todas las acciones monádicas que desea utilizar de esta manera, puede entrelazar las mónadas tanto como desee sin ningún tipo de levantamiento tedioso.