У яких ситуаціях слід 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, незалежно від того, в якій монаді ви знаходитесь. В основному liftIO дорівнює використанню змінної кількості підйомів. Спочатку це може здатися зайвим, але використання liftIO має одну велику перевагу: це робить ваш код вводу-виводу незалежним від фактичної конструкції Monad, щоб ви могли повторно використовувати той самий код, незалежно від кількості шарів, з якої була побудована ваша остаточна Monad (це досить важливо при написанні монадного трансформатора).
З іншого боку, liftIO не продається безкоштовно, як це робить ліфт: трансформатори Monad, які ви використовуєте, повинні мати для нього підтримку, наприклад, 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
typeclass.
Написати 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
цього достатньо, оскільки тоді я можу змінити стек монад, і код все ще працює.