Haskell: Lift vs LiftIO

82

In welchen Situationen sollte liftIOverwendet werden? Wenn ich verwende ErrorT String IO, liftfunktioniert die Funktion zum Aufheben von E / A-Aktionen ErrorTund liftIOerscheint daher überflüssig.

Lachlan
quelle

Antworten:

93

lifthebt immer von der "vorherigen" Ebene ab. Wenn Sie von der zweiten Schicht abheben müssen, benötigen Sie lift . liftund so weiter.

Hebt sich dagegen liftIOimmer 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
Roman Cheplyaka
quelle
33
Ich werde im Allgemeinen verwenden liftIO, um auf die E / A-Ebene zu heben, auch wenn dies liftausreicht, da ich dann den Monadenstapel ändern kann und der Code immer noch funktioniert.
John L
14
@ John: guter Punkt. Und es macht auch deutlich, dass Sie IO heben und keine andere Monade.
Roman Cheplyaka
Ich bin ein Neuling: liftIn dieser Antwort (und der Frage) wird angenommen, dass sie von stammt Control.Monad.Trans.Class, denke ich? Nicht das Monadic liftingoder das allgemeine Heben wie im ersten Abschnitt hier beschrieben ?
Nawaz
37

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!).

Cristiano Paris
quelle
1

Alle vorherigen Antworten erklären den Unterschied recht gut. Ich wollte nur ein wenig Licht in das Innenleben bringen, damit es leichter zu verstehen liftIOist, 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 IOMonade ist die Definition recht einfach.

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

instance MonadIO IO where
  liftIO = id

So einfach ... liftIOist in der Tat nur idfür die IOMonade und im Grunde IOdie 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 MonadIOInstanz für jede dieser Monadentransformatorschichten. Zum Beispiel des MonadIOvon Beispiel MaybeT merfordert mvon sein MonadIOals auch typeclass.

Das Schreiben einer MonadIOInstanz ist im Grunde auch eine sehr einfache Aufgabe. Denn MaybeT mes 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 $ myIOActionoder nur liftIO myIOAction. Wenn Sie darüber nachdenken, lift . liftIObringt Sie jeder eine Ebene nach unten in den Stapel, bis er bis zu dem Punkt gräbt, IOan dem er liftIOdefiniert ist, idund mit demselben Code wie oben zusammengesetzt liftendet.

Dies ist im Grunde der Grund, warum unabhängig von der Konfiguration des Transformatorstapels alle darunter liegenden Schichten Mitglieder sind MonadIOund MonadTranseine einzige liftIOin Ordnung ist.

Reduzieren
quelle