Haskell: lift vs liftIO

82

¿En qué situaciones se debe liftIOutilizar? Cuando estoy usando ErrorT String IO, la liftfunción funciona para elevar las acciones de IO ErrorT, por lo que liftIOparece superflua.

Lachlan
fuente

Respuestas:

93

liftsiempre se eleva desde la capa "anterior". Si necesita levantar de la segunda capa, necesitará lift . lifty así sucesivamente.

Por otro lado, liftIOsiempre 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
Roman Cheplyaka
fuente
33
Generalmente usaré liftIOpara subir a la capa IO incluso si liftes suficiente, porque entonces puedo cambiar la pila de mónadas y el código aún funciona.
John L
14
@John: buen punto. Y también hace que sea obvio que estás elevando IO y no cualquier otra mónada.
Roman Cheplyaka
Soy un novato: liften esta respuesta (y la pregunta) se supone que es de Control.Monad.Trans.Class, ¿supongo? ¿No es el Monadic liftingo el levantamiento general como se describe en la primera sección aquí ?
Nawaz
37

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!).

Cristiano Paris
fuente
1

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 liftIOno 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 IOmó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 ... liftIOes de hecho solo idpara la IOmónada y básicamente IOes 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 MonadIOinstancia para cada una de esas capas de transformador de mónada. Por ejemplo, la MonadIOinstancia de también MaybeT mrequiere mser de MonadIOtypeclass.

Escribir una MonadIOinstancia también es básicamente una tarea muy simple. Porque MaybeT mse 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 $ myIOActiono simplemente liftIO myIOAction. Si lo piensa, cada uno lift . liftIOlo llevará una capa hacia abajo en la pila hasta que cava hasta IOdonde liftIOse define como idy finaliza con el mismo código que el compuesto liftanterior.

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 MonadIOy MonadTransuna sola liftIOesté bien.

Redu
fuente