87 Stimmen

Haskell: lift vs liftIO

In welchen Situationen sollten liftIO verwendet werden? Wenn ich Folgendes verwende ErrorT String IO die lift Funktion funktioniert, um IO-Aktionen in ErrorT also liftIO scheint überflüssig zu sein.

101voto

Roman Cheplyaka Punkte 36548

lift hebt immer von der "vorherigen" Ebene ab. Wenn Sie von der zweiten Schicht aus heben müssen, benötigen Sie lift . lift und so weiter.

Andererseits, liftIO hebt immer von der IO-Schicht ab (die sich, wenn vorhanden, immer am unteren Ende des Stapels befindet). Wenn Sie also mehr als 2 Schichten von Monaden haben, werden Sie Folgendes 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

40voto

Cristiano Paris Punkte 1764

LiftIO ist nur eine Abkürzung zur IO-Monade, egal in welcher Monade Sie sich befinden. Im Grunde genommen ist liftIO gleichbedeutend mit der Verwendung einer variablen Anzahl von Lifts. Auf den ersten Blick mag dies redundant klingen, aber die Verwendung von liftIO hat einen großen Vorteil: Sie macht Ihren IO-Code unabhängig von der eigentlichen Monaden-Konstruktion, so dass Sie denselben Code wiederverwenden können, unabhängig von der Anzahl der Schichten, aus denen Ihre endgültige Monade aufgebaut ist (dies ist sehr wichtig, wenn Sie einen Monaden-Transformator schreiben).

Andererseits gibt es liftIO nicht umsonst, wie lift es tut: die Monad-Transformatoren, die Sie verwenden, müssen es unterstützen, z.B. muss die Monad, in der Sie sich befinden, eine Instanz der MonadIO-Klasse sein, aber die meisten Monads tun das heutzutage (und natürlich wird der Type-Checker dies zur Kompilierzeit für Sie überprüfen: das ist die Stärke von Haskell!)

8voto

Redu Punkte 22159

Die bisherigen Antworten erklären den Unterschied recht gut. Ich wollte nur das Innenleben etwas beleuchten, damit es leichter zu verstehen ist, wie liftIO ist nichts Magisches (für unerfahrene Haskeller wie mich).

liftIO :: IO a -> m a

ist ein kluges Instrument, auf dem man aufbauen kann

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

y am häufigsten verwendet, wenn die untere Monade IO . Für die IO Monade ist ihre Definition recht einfach.

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

instance MonadIO IO where
  liftIO = id

So einfach ist das... liftIO ist in Wirklichkeit nur id für die IO Monade und grundsätzlich IO ist die einzige, die in der Definition der Typklasse enthalten ist.

Die Sache ist die, wenn wir einen Monadentyp haben, der aus mehreren Schichten von Monadentransformatoren besteht über IO haben wir besser eine MonadIO Instanz für jede dieser Monaden-Transformationsebenen. Zum Beispiel die MonadIO Instanz von MaybeT m erfordert m zu sein von MonadIO auch die Typklasse.

Schreiben einer MonadIO Instanz ist im Grunde auch eine sehr einfache Aufgabe. Für MaybeT m ist wie folgt definiert

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, Sie haben einen 4-Schicht-Transformator-Stapel, dann müssen Sie entweder lift . lift . lift . lift $ myIOAction oder einfach liftIO myIOAction . Wenn man darüber nachdenkt, ist jeder lift . liftIO bringt Sie eine Ebene tiefer in den Stapel, bis er ganz nach unten gräbt IO wo liftIO ist definiert als id und endet mit demselben Code wie die zusammengesetzten lift s oben.

Dies ist der Grund, warum unabhängig von der Konfiguration des Transformatorstapels, sofern alle darunter liegenden Ebenen Mitglieder von MonadIO y MonadTrans eine einzige liftIO ist genau richtig.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X