Haskell: лифт против лифта

83

В каких ситуациях следует liftIOиспользовать? Когда я использую ErrorT String IO, liftфункция работает, чтобы поднять действия ввода-вывода ErrorT, поэтому liftIOкажется излишней.

Лахлан
источник

Ответы:

94

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
Роман Чепляка
источник
34
Я обычно использую liftIOдля подъема на уровень ввода-вывода, даже если этого liftдостаточно, потому что тогда я могу изменить стек монад, и код все еще работает.
Джон Л.
14
@ Джон: хороший момент. А также становится очевидным, что вы поднимаете IO, а не какую-либо другую монаду.
Роман Чепляка,
Я новичок: liftв этом ответе (и вопросе) предполагается, что он Control.Monad.Trans.Class, я думаю? Не тот Monadic liftingили общий подъем, как описано в первом разделе здесь ?
Nawaz
38

liftIO - это просто ярлык для IO Monad, в какой бы монаде вы ни находились. В основном, liftIO означает использование переменного количества подъемов. Сначала это может показаться избыточным, но использование liftIO имеет одно большое преимущество: он делает ваш код ввода-вывода независимым от фактической конструкции монады, поэтому вы можете повторно использовать один и тот же код независимо от количества слоев, из которых была построена ваша окончательная монада (это очень важно. при написании преобразователя монад).

С другой стороны, liftIO не предоставляется бесплатно, в отличие от lift: трансформаторы Monad, которые вы используете, должны иметь его поддержку, например, монада, в которой вы находитесь, должна быть экземпляром класса MonadIO, но большинство монад в настоящее время поддерживают его. (и, конечно же, программа проверки типов проверит это для вас во время компиляции: в этом сила Haskell!).

Криштиану Пэрис
источник
2

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

Уменьшить
источник