В каких ситуациях следует 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достаточно, потому что тогда я могу изменить стек монад, и код все еще работает.