In welchen Situationen sollte liftIO
verwendet werden? Wenn ich verwende ErrorT String IO
, lift
funktioniert die Funktion zum Aufheben von E / A-Aktionen ErrorT
und liftIO
erscheint daher überflüssig.
Antworten:
lift
hebt immer von der "vorherigen" Ebene ab. Wenn Sie von der zweiten Schicht abheben müssen, benötigen Sie lift . lift
und so weiter.
Hebt sich dagegen liftIO
immer von der E / A-Schicht ab (die sich, falls vorhanden, immer am Boden des Stapels befindet). Wenn Sie also mehr als 2 Schichten Monaden haben, werden Sie es zu schätzen wissen liftIO
.
Vergleichen Sie den Typ des Arguments in den folgenden Lambdas:
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
lift
In dieser Antwort (und der Frage) wird angenommen, dass sie von stammt Control.Monad.Trans.Class
, denke ich? Nicht das Monadic lifting
oder das allgemeine Heben wie im ersten Abschnitt hier beschrieben ?
liftIO ist nur eine Abkürzung zur E / A-Monade, unabhängig davon, in welcher Monade Sie sich befinden. Grundsätzlich entspricht liftIO der Verwendung einer variablen Anzahl von Aufzügen. Das mag zunächst redundant klingen, aber die Verwendung von liftIO hat einen großen Vorteil: Ihr IO-Code ist unabhängig von der tatsächlichen Monad-Konstruktion, sodass Sie denselben Code unabhängig von der Anzahl der Ebenen, aus denen Ihre endgültige Monad erstellt wurde, wiederverwenden können (dies ist sehr wichtig) beim Schreiben eines Monadentransformators).
Auf der anderen Seite ist liftIO nicht kostenlos, wie es lift ist: Die Monad-Transformatoren, die Sie verwenden, müssen Unterstützung dafür haben, z. B. muss die Monade, in der Sie sich befinden, eine Instanz der MonadIO-Klasse sein, aber die meisten Monaden heutzutage (Und natürlich überprüft der Typprüfer dies beim Kompilieren für Sie: Das ist die Stärke von Haskell!).
Alle vorherigen Antworten erklären den Unterschied recht gut. Ich wollte nur ein wenig Licht in das Innenleben bringen, damit es leichter zu verstehen liftIO
ist, dass etwas nicht magisch ist (für die unerfahrenen Haskeller wie mich).
liftIO :: IO a -> m a
ist ein weises Werkzeug, auf dem man einfach aufbauen kann
lift :: (Control.Monad.Trans.Class.MonadTrans t, Monad m) => m a -> t m a
und am häufigsten verwendet, wenn die untere Monade ist IO
. Für die IO
Monade ist die Definition recht einfach.
class (Monad m) => MonadIO m where
liftIO :: IO a -> m a
instance MonadIO IO where
liftIO = id
So einfach ... liftIO
ist in der Tat nur id
für die IO
Monade und im Grunde IO
die einzige, die unter die Definition der Typklasse fällt.
Die Sache ist, wenn wir einen Monadentyp haben, der aus mehreren Schichten von Monadentransformatoren besteht IO
, haben wir besser eine MonadIO
Instanz für jede dieser Monadentransformatorschichten. Zum Beispiel des MonadIO
von Beispiel MaybeT m
erfordert m
von sein MonadIO
als auch typeclass.
Das Schreiben einer MonadIO
Instanz ist im Grunde auch eine sehr einfache Aufgabe. Denn MaybeT m
es ist definiert wie
instance (MonadIO m) => MonadIO (MaybeT m) where
liftIO = lift . liftIO
oder für StateT s m
instance (MonadIO m) => MonadIO (StateT s m) where
liftIO = lift . liftIO
Sie sind alle gleich. Stellen Sie sich vor, wenn Sie einen 4-Schicht-Transformatorstapel haben, müssen Sie dies entweder tun lift . lift . lift . lift $ myIOAction
oder nur liftIO myIOAction
. Wenn Sie darüber nachdenken, lift . liftIO
bringt Sie jeder eine Ebene nach unten in den Stapel, bis er bis zu dem Punkt gräbt, IO
an dem er liftIO
definiert ist, id
und mit demselben Code wie oben zusammengesetzt lift
endet.
Dies ist im Grunde der Grund, warum unabhängig von der Konfiguration des Transformatorstapels alle darunter liegenden Schichten Mitglieder sind MonadIO
und MonadTrans
eine einzige liftIO
in Ordnung ist.
liftIO
, um auf die E / A-Ebene zu heben, auch wenn dieslift
ausreicht, da ich dann den Monadenstapel ändern kann und der Code immer noch funktioniert.