Haskell: lift vs liftIO


82

Nên liftIOdùng trong những trường hợp nào? Khi tôi đang sử dụng ErrorT String IO, liftchức năng hoạt động để nâng các hành động IO vào ErrorT, vì vậy liftIOcó vẻ như không cần thiết.

Câu trả lời:


93

liftluôn nâng từ lớp "trước". Nếu bạn cần nâng từ lớp thứ hai, bạn sẽ cầnlift . lift , v.v.

Mặt khác, liftIOluôn nâng lên từ lớp IO (lớp này luôn nằm ở dưới cùng của ngăn xếp). Vì vậy, nếu bạn có nhiều hơn 2 lớp monads, bạn sẽ đánh giá cao liftIO.

So sánh kiểu đối số trong lambdas sau:

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

33
Nói chung, tôi sẽ sử dụng liftIOđể nâng lên lớp IO ngay cả khi đã liftđủ, vì sau đó tôi có thể thay đổi ngăn xếp đơn nguyên và mã vẫn hoạt động.
John L

14
@John: điểm tốt. Và cũng có thể thấy rõ rằng bạn đang nâng IO chứ không phải bất kỳ đơn nguyên nào khác.
Roman Cheplyaka 13/10/10

Tôi là một người mới: lifttrong câu trả lời này (và câu hỏi) được giả định là từ Control.Monad.Trans.Class, tôi đoán? Không phải Monadic liftinghoặc nâng chung như được mô tả trong phần đầu tiên ở đây ?
Nawaz

37

liftIO chỉ là một lối tắt đến Đơn vị IO, cho dù bạn đang ở Đơn vị nào. Về cơ bản, liftIO tương đương với việc sử dụng một số thang máy thay đổi. Lúc đầu, điều này nghe có vẻ thừa nhưng sử dụng liftIO có một lợi thế lớn: nó làm cho mã IO của bạn không phụ thuộc vào cấu trúc Đơn nguyên thực tế, do đó bạn có thể sử dụng lại cùng một mã bất kể số lớp Đơn nguyên cuối cùng của bạn đã được xây dựng (điều này khá quan trọng khi viết máy biến áp đơn nguyên).

Mặt khác, liftIO không đến miễn phí, như lift có: các máy biến áp Monad mà bạn đang sử dụng phải có hỗ trợ cho nó, ví dụ: Monad bạn đang tham gia phải là một phiên bản của lớp MonadIO, nhưng hầu hết các Monad ngày nay đều có (và tất nhiên, trình kiểm tra kiểu sẽ kiểm tra điều này cho bạn tại thời điểm biên dịch: đó là điểm mạnh của Haskell!).


1

Các câu trả lời trước đây đều giải thích sự khác biệt khá rõ ràng. Tôi chỉ muốn làm sáng tỏ một số hoạt động bên trong để có thể dễ hiểu hơn về việc làm thế nào liftIOkhông phải là thứ gì đó ma thuật (đối với những Haskellers mới làm quen với tôi).

liftIO :: IO a -> m a

là một công cụ khôn ngoan chỉ được xây dựng dựa trên

lift :: (Control.Monad.Trans.Class.MonadTrans t, Monad m) => m a -> t m a

thường được sử dụng nhất khi đơn nguyên dưới cùng IO. Đối với IOđơn nguyên, định nghĩa của nó khá đơn giản.

class (Monad m) => MonadIO m where
  liftIO :: IO a -> m a

instance MonadIO IO where
  liftIO = id

Đơn giản đó ... liftIOtrên thực tế chỉ iddành cho IOđơn nguyên và về cơ bản IOlà đơn nguyên duy nhất nằm trong định nghĩa của lớp kiểu.

Vấn đề là, khi chúng ta có một loại đơn nguyên bao gồm nhiều lớp máy biến áp đơn nguyên IO, tốt hơn chúng ta nên có một MonadIOthể hiện cho mỗi một trong số các lớp máy biến áp đơn nguyên đó. Ví dụ, MonadIOtrường hợp của MaybeT myêu cầu cũng mphải là MonadIOtypeclass.

Viết một MonadIOphiên bản về cơ bản cũng là một công việc rất đơn giản. Vì MaybeT mnó được định nghĩa như

instance (MonadIO m) => MonadIO (MaybeT m) where
  liftIO = lift . liftIO

hoặc là StateT s m

instance (MonadIO m) => MonadIO (StateT s m) where
  liftIO = lift . liftIO

tất cả họ đều như nhau. Hãy tưởng tượng khi bạn có một ngăn xếp máy biến áp 4 lớp thì bạn cần làm lift . lift . lift . lift $ myIOActionhoặc chỉ liftIO myIOAction. Nếu bạn nghĩ về nó, mọi thứ lift . liftIOsẽ đưa bạn xuống một lớp trong ngăn xếp cho đến khi nó đào sâu xuống IOvị trí liftIOđược xác định là idvà hoàn thành với cùng một đoạn mã như đã soạn liftở trên.

Vì vậy, về cơ bản đây là lý do tại sao bất kể cấu hình ngăn xếp máy biến áp như thế nào, với điều kiện tất cả các lớp bên dưới đều là thành viên của MonadIOMonadTransmột lớp duy nhất liftIOlà ổn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.