Haskell: ліфт проти liftIO


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

14
@ Джон: хороший момент. А також стає очевидним, що ви піднімаєте введення-виведення, а не будь-яку іншу монаду.
Роман Чепляка

Я новачок: liftу цій відповіді (і на запитання), мабуть Control.Monad.Trans.Class, передбачається? Чи не Monadic liftingзагальний підйом, як описано в першому розділі тут ?
Nawaz

38

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

З іншого боку, liftIO не продається безкоштовно, як це робить ліфт: трансформатори Monad, які ви використовуєте, повинні мати для нього підтримку, наприклад, 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також бути 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цілком чудовий.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.