die Art der liftIO
Denken Sie daran:
liftIO :: MonadIO m => IO a -> m a
Wichtig ist, dass das erste Argument ein konkreter IO
Wert sein muss. Das heißt, wenn Sie einen Ausdruck liftIO x
haben, dann muss x
vom Typ IO a
sein.
Wenn ein Haskell Funktion universell quantifiziert wird (eine implizite oder explizite forall
verwendet wird), dann bedeutet die Funktion Anrufer wählt, was die Art von Variable ersetzt wird. Als Beispiel betrachten Sie die id
Funktion: Es hat Typ a -> a
, aber wenn Sie den Ausdruck id True
auswerten, dann id
nimmt den Typ Bool -> Bool
, weil a
als Bool
Typ instanziiert wird.
Betrachten Sie nun Ihr erstes Beispiel wieder:
run2IO :: MonadIO m => m Integer -> m Integer
run2IO foo = liftIO bar
Das foo
Argument hier völlig irrelevant ist, so alles, was der Ausdruck liftIO bar
eigentlich zählt, ist.Da liftIO
erfordert, ist das erste Argument vom Typ IO a
, dann bar
muss vom Typ IO a
sein. bar
ist jedoch polymorph: Es hat tatsächlich den Typ MonadIO m => m Integer
.
Glücklicherweise hat IO
eine MonadIO
Instanz, so dass der Wert bar
instanziiert istIO
mit IO Integer
werden, was in Ordnung ist, weil bar
universell quantifiziert, so dass ihr Instanziierung durch seine Verwendung gewählt wird.
Betrachten Sie nun die andere Situation, in der liftIO foo
stattdessen verwendet wird. Diese scheint wie es ist das gleiche, aber es ist eigentlich gar nicht: dieses Mal ist der MonadIO m => m Integer
Wert ein Argument für die Funktion, kein separater Wert. Die Quantifizierung erfolgt über die gesamte Funktion, nicht den Einzelwert. Um zu verstehen, mehr dies intuitiv, könnte es hilfreich sein, id
wieder zu betrachten, aber dieses Mal, betrachtet seine Definition:
id :: a -> a
id x = x
In diesem Fall x
kann nicht instanziert werden, um zu sein Bool
in seiner Definition, denn das würde bedeuten, id
konnte nur an Bool
Werten arbeiten, was offensichtlich falsch ist. Effektiv muss innerhalb der Implementierung von id
, x
vollständig generisch verwendet werden - es kann nicht zu einem bestimmten Typ instanziiert werden, da dies die Parametergarantien verletzen würde.
daher in Ihrer run2IO
Funktion, foo
muss vollständig generisch als willkürlichen MonadIO
Wert, nicht eine bestimmte MonadIO
Instanz verwendet werden. Der Anruf liftIO
versucht, die spezifische Instanz IO
zu verwenden, die nicht zulässig ist, da der Anrufer möglicherweise keinen IO
-Wert angibt.
Es ist natürlich möglich, dass Sie das Argument an die Funktion in der gleichen Art und Weise quantifiziert werden könnten wollen als bar
sind; Das heißt, Sie möchten möglicherweise, dass die Instanziierung von der Implementierung und nicht vom Aufrufer ausgewählt wird. In diesem Fall können Sie die RankNTypes
Spracherweiterung verwenden, um eine andere Art mit einer expliziten forall
angeben:
{-# LANGUAGE RankNTypes #-}
run3IO :: MonadIO m => (forall m1. MonadIO m1 => m1 Integer) -> m Integer
run3IO foo = liftIO foo
Dies wird typecheck, aber es ist nicht eine sehr nützliche Funktion.
Kann ich fragen, wo Sie Informationen über das Verhalten des Compilers erhalten können? Ich habe etwa 6 Monate in Haskell programmiert und würde gerne einen Schritt nach vorn machen und erfahren, was unter der Haube passiert. –
Ähm, was meinst du mit "Informationen zum Compiler-Verhalten"? Sie können sich sicherlich [den Quellcode] (http://git.haskell.org/ghc.git) ansehen, um zu erfahren, was GHC tut. Ich lese eigentlich nie viel davon - ich finde es viel nützlicher, einfach viel Code zu schreiben und zu sehen, was passiert. – leftaroundabout
Es ist am besten, nicht daran zu denken, über solche Verhaltensweisen zu lernen, wie den Compiler zu lernen, da dieses Verhalten von der Sprache selbst gut spezifiziert wird. Die Art der Quantifizierung ist durch Typensignaturen gegeben, ist kein ungewöhnlicher Stolperpunkt, und es ist eine gute Sache, sich explizit damit zu beschäftigen, anstatt mit einer Art "Hurme, eines Tages werde ich es verstehen" -Antwort zu übergehen. –