У яких ситуаціях слід 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також бути MonadIOtypeclass.
Написати 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цього достатньо, оскільки тоді я можу змінити стек монад, і код все ще працює.