W jakich sytuacjach należy liftIO
użyć? Kiedy używam ErrorT String IO
, lift
funkcja działa w celu podniesienia akcji IO do ErrorT
, więc liftIO
wydaje się zbędna.
Odpowiedzi:
lift
zawsze podnosi się z „poprzedniej” warstwy. Jeśli chcesz podnieść z drugiej warstwy, potrzebujesz lift . lift
i tak dalej.
Z drugiej strony, liftIO
zawsze podnosi się z warstwy IO (która, jeśli jest obecna, zawsze znajduje się na dole stosu). Jeśli więc masz więcej niż 2 warstwy monad, z pewnością docenisz liftIO
.
Porównaj typ argumentu w następujących lambdach:
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 to tylko skrót do monady IO, bez względu na to, w której monadzie się znajdujesz. W zasadzie liftIO to użycie zmiennej liczby wind. Na początku może się to wydawać zbędne, ale użycie liftIO ma jedną wielką zaletę: sprawia, że twój kod IO jest niezależny od faktycznej konstrukcji Monady, więc możesz ponownie użyć tego samego kodu bez względu na liczbę warstw, z których została zbudowana twoja ostateczna Monada (jest to dość ważne podczas pisania transformatora monadowego).
Z drugiej strony, liftIO nie przychodzi za darmo, jak to robi winda: transformatory Monad, których używasz, muszą mieć dla niego wsparcie, np. Monada, w której jesteś, musi być instancją klasy MonadIO, ale większość dzisiejszych Monad to robi. (i oczywiście narzędzie do sprawdzania typów sprawdzi to podczas kompilacji: to jest siła Haskella!).
Wszystkie poprzednie odpowiedzi dość dobrze wyjaśniają różnicę. Chciałem tylko rzucić trochę światła na wewnętrzne funkcjonowanie, aby łatwiej było zrozumieć, dlaczego liftIO
nie jest to coś magicznego (dla początkujących Haskellerów, takich jak ja).
liftIO :: IO a -> m a
jest mądrym narzędziem, na którym po prostu buduj
lift :: (Control.Monad.Trans.Class.MonadTrans t, Monad m) => m a -> t m a
i najczęściej używany, gdy jest dolna monada IO
. W przypadku IO
monady definicja jest dość prosta.
class (Monad m) => MonadIO m where
liftIO :: IO a -> m a
instance MonadIO IO where
liftIO = id
To proste ... liftIO
jest w rzeczywistości tylko id
dla IO
monady i zasadniczo IO
jest jedyną, która mieści się w definicji klasy typu.
Rzecz w tym, że kiedy mamy typ monady, który składa się z kilku warstw transformatorów monadowych IO
, lepiej mieć MonadIO
instancję dla każdej z tych warstw transformatora monadowego. Na przykład MonadIO
instancja MaybeT m
wymaga, m
aby była również MonadIO
typeklasą.
Pisanie MonadIO
instancji jest również w zasadzie bardzo prostym zadaniem. Ponieważ MaybeT m
jest zdefiniowany jako
instance (MonadIO m) => MonadIO (MaybeT m) where
liftIO = lift . liftIO
albo za StateT s m
instance (MonadIO m) => MonadIO (StateT s m) where
liftIO = lift . liftIO
oni wszyscy są tacy sami. Wyobraź sobie, że masz 4-warstwowy stos transformatorów, wtedy albo musisz to zrobić, lift . lift . lift . lift $ myIOAction
albo po prostu liftIO myIOAction
. Jeśli się nad tym zastanowić, każdy lift . liftIO
przeniesie Cię o jedną warstwę w dół w stosie, aż zacznie kopać w dół do miejsca, IO
w którym liftIO
jest zdefiniowane jako id
i kończy się tym samym kodem, co skomponowane lift
powyżej.
Więc dlaczego to jest w zasadzie niezależnie od konfiguracji transformator stosu, pod warunkiem, że wszystkie warstwy pod nią są członkowie MonadIO
i MonadTrans
jeden liftIO
jest w porządku.
liftIO
do podniesienia do warstwy IO, nawet jeślilift
jest to wystarczające, ponieważ wtedy mogę zmienić stos monady, a kod nadal działa.