¿En qué situaciones se debe liftIO
utilizar? Cuando estoy usando ErrorT String IO
, la lift
función funciona para elevar las acciones de IO ErrorT
, por lo que liftIO
parece superflua.
Respuestas:
lift
siempre se eleva desde la capa "anterior". Si necesita levantar de la segunda capa, necesitará lift . lift
y así sucesivamente.
Por otro lado, liftIO
siempre se eleva desde la capa IO (que, cuando está presente, siempre está en la parte inferior de la pila). Entonces, si tienes más de 2 capas de mónadas, lo agradecerás liftIO
.
Compare el tipo de argumento en las siguientes lambdas:
type T = ReaderT Int (WriterT String IO) Bool
> :t \x -> (lift x :: T)
\x -> (lift x :: T) :: WriterT String IO Bool -> T
> :t \x -> (liftIO x :: T)
\x -> (liftIO x :: T) :: IO Bool -> T
liftIO es solo un acceso directo a IO Monad, cualquiera que sea la Monad en la que se encuentre. Básicamente, liftIO equivale a usar un número variable de ascensores. Al principio, esto puede parecer redundante, pero usar liftIO tiene una gran ventaja: hace que su código IO sea independiente de la construcción real de Monad, por lo que puede reutilizar el mismo código sin importar la cantidad de capa de la que se haya construido su Monad final (esto es bastante importante al escribir un transformador de mónada).
Por otro lado, liftIO no viene gratis, como lo hace lift: los transformadores Monad que está utilizando deben tener soporte para él, por ejemplo, el Monad en el que se encuentra debe ser una instancia de la clase MonadIO, pero la mayoría de las Monad hoy en día lo hacen. (y, por supuesto, el verificador de tipos comprobará esto por usted en el momento de la compilación: ¡esa es la fortaleza de Haskell!).
Todas las respuestas anteriores explican bastante bien la diferencia. Solo quería arrojar algo de luz sobre el funcionamiento interno para que sea más fácil entender cómo liftIO
no es algo mágico (para los Haskellers novatos como yo).
liftIO :: IO a -> m a
es una herramienta inteligente basada en
lift :: (Control.Monad.Trans.Class.MonadTrans t, Monad m) => m a -> t m a
y se utiliza con mayor frecuencia cuando la mónada inferior es IO
. Para la IO
mónada, su definición es bastante simple.
class (Monad m) => MonadIO m where
liftIO :: IO a -> m a
instance MonadIO IO where
liftIO = id
Así de simple ... liftIO
es de hecho solo id
para la IO
mónada y básicamente IO
es el único que entra dentro de la definición de la clase de tipo.
El caso es que, cuando tenemos un tipo de mónada que se compone de varias capas de transformadores de mónada IO
, es mejor tener una MonadIO
instancia para cada una de esas capas de transformador de mónada. Por ejemplo, la MonadIO
instancia de también MaybeT m
requiere m
ser de MonadIO
typeclass.
Escribir una MonadIO
instancia también es básicamente una tarea muy simple. Porque MaybeT m
se define como
instance (MonadIO m) => MonadIO (MaybeT m) where
liftIO = lift . liftIO
o por StateT s m
instance (MonadIO m) => MonadIO (StateT s m) where
liftIO = lift . liftIO
Son todos iguales. Imagínese cuando tiene una pila de transformadores de 4 capas, entonces necesita hacerlo lift . lift . lift . lift $ myIOAction
o simplemente liftIO myIOAction
. Si lo piensa, cada uno lift . liftIO
lo llevará una capa hacia abajo en la pila hasta que cava hasta IO
donde liftIO
se define como id
y finaliza con el mismo código que el compuesto lift
anterior.
Básicamente, esta es la razón por la que, independientemente de la configuración de la pila de transformadores, siempre que todas las capas subyacentes sean miembros de MonadIO
y MonadTrans
una sola liftIO
esté bien.
liftIO
para subir a la capa IO incluso silift
es suficiente, porque entonces puedo cambiar la pila de mónadas y el código aún funciona.