В каких ситуациях следует liftIO
использовать? Когда я использую ErrorT String IO
, lift
функция работает, чтобы поднять действия ввода-вывода ErrorT
, поэтому liftIO
кажется излишней.
Ответы:
lift
всегда поднимается с «предыдущего» слоя. Если вам нужно поднять со второго слоя, вам понадобится lift . lift
и так далее.
С другой стороны, liftIO
всегда поднимается от уровня ввода-вывода (который, если он присутствует, всегда находится внизу стека). Итак, если у вас более 2 слоев монад, вы оцените liftIO
.
Сравните тип аргумента в следующих лямбдах:
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 - это просто ярлык для IO Monad, в какой бы монаде вы ни находились. В основном, liftIO означает использование переменного количества подъемов. Сначала это может показаться избыточным, но использование liftIO имеет одно большое преимущество: он делает ваш код ввода-вывода независимым от фактической конструкции монады, поэтому вы можете повторно использовать один и тот же код независимо от количества слоев, из которых была построена ваша окончательная монада (это очень важно. при написании преобразователя монад).
С другой стороны, liftIO не предоставляется бесплатно, в отличие от lift: трансформаторы Monad, которые вы используете, должны иметь его поддержку, например, монада, в которой вы находитесь, должна быть экземпляром класса MonadIO, но большинство монад в настоящее время поддерживают его. (и, конечно же, программа проверки типов проверит это для вас во время компиляции: в этом сила Haskell!).
Все предыдущие ответы хорошо объясняют разницу. Я просто хотел пролить свет на внутреннюю работу, чтобы было легче понять, почему liftIO
что-то не волшебное (для начинающих хаскеллеров вроде меня).
liftIO :: IO a -> m a
это мудрый инструмент, просто опирающийся на
lift :: (Control.Monad.Trans.Class.MonadTrans t, Monad m) => m a -> t m a
и чаще всего используется, когда используется нижняя монада IO
. Для IO
монады это определение довольно простое.
class (Monad m) => MonadIO m where
liftIO :: IO a -> m a
instance MonadIO IO where
liftIO = id
Это просто ... liftIO
на самом деле только id
для IO
монады и, по сути, IO
единственное, что входит в определение класса типа.
Дело в том, что когда у нас есть тип монады, который состоит из нескольких слоев преобразователей монад IO
, нам лучше иметь MonadIO
экземпляр для каждого из этих слоев преобразователей монад. Например, MonadIO
экземпляр MaybeT m
требует, m
чтобы он былMonadIO
класс типов , а также.
Написание MonadIO
экземпляра - тоже очень простая задача. Поскольку MaybeT m
это определяется как
instance (MonadIO m) => MonadIO (MaybeT m) where
liftIO = lift . liftIO
или для StateT s m
instance (MonadIO m) => MonadIO (StateT s m) where
liftIO = lift . liftIO
они все одинаковы. Представьте, что у вас есть 4-слойный трансформаторный стек, который вам либо нужно, lift . lift . lift . lift $ myIOAction
либо просто сделать liftIO myIOAction
. Если вы думаете об этом, каждый lift . liftIO
будет считать вас один слой вниз в стеке вверх , пока он не копает весь путь вниз IO
в котором liftIO
определяется как id
и дорабатывает с тем же кодом , как в составе lift
с выше.
Вот почему, в основном, независимо от конфигурации стека трансформаторов, при условии, что все нижележащие слои являются его членами, MonadIO
а MonadTrans
один liftIO
- в порядке.
liftIO
для подъема на уровень ввода-вывода, даже если этогоlift
достаточно, потому что тогда я могу изменить стек монад, и код все еще работает.