Nên liftIO
dùng trong những trường hợp nào? Khi tôi đang sử dụng ErrorT String IO
, lift
chức năng hoạt động để nâng các hành động IO vào ErrorT
, vì vậy liftIO
có vẻ như không cần thiết.
Câu trả lời:
lift
luô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, liftIO
luô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
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!).
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 liftIO
khô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
và 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 đó ... liftIO
trên thực tế chỉ id
dành cho IO
đơn nguyên và về cơ bản IO
là đơ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 MonadIO
thể hiện cho mỗi một trong số các lớp máy biến áp đơn nguyên đó. Ví dụ, MonadIO
trường hợp của MaybeT m
yêu cầu cũng m
phải là MonadIO
typeclass.
Viết một MonadIO
phiên bản về cơ bản cũng là một công việc rất đơn giản. Vì MaybeT m
nó đượ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 $ myIOAction
hoặc chỉ liftIO myIOAction
. Nếu bạn nghĩ về nó, mọi thứ lift . liftIO
sẽ đưa bạn xuống một lớp trong ngăn xếp cho đến khi nó đào sâu xuống IO
vị trí liftIO
được xác định là id
và 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 MonadIO
và MonadTrans
một lớp duy nhất liftIO
là ổn.
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.