2016-09-04 3 views
3

Dieses Programm kompiliert ohne Probleme:Haskell: function Unterschrift

bar :: MonadIO m 
    => m String 
bar = undefined 

run2IO :: MonadIO m 
     => m String 
     -> m String 
run2IO foo = liftIO bar 

Als ich bar-foo (Argument Name) zu ändern,

run2IO :: MonadIO m 
     => m String 
     -> m String 
run2IO foo = liftIO foo 

ich:

konnte nicht Übereinstimmungstyp 'm' mit 'IO' 'm' ist eine feste Typvariable, die angebunden istdie Art Signatur für run2IO :: MonadIO m => m String -> m String ...

Erwartete Typ: IO String Actual Typ: m String ...

Warum sind die 2 Fälle sind nicht gleichwertig?

Antwort

5

In der ersten verwenden Sie liftIO unter bar. Das erfordert tatsächlich bar :: IO String. Nun, IO passiert (trivial) eine Instanz auf MonadIO, so funktioniert das - der Compiler wirft einfach den Polymorphismus bar.

Im zweiten Fall, der Compiler erhält nicht besonders Monade zu entscheiden, was als Typ der foo zu verwenden: es durch die Umgebung festgelegt ist, das heißt die Anrufer können, was MonadIO Instanz entscheiden, sollte es sein. Um wieder die Freiheit zu erhalten zu IO als Monade zu wählen, würden Sie die folgende Signatur benötigen:

{-# LANGUAGE Rank2Types, UnicodeSyntax #-} 

run2IO' :: MonadIO m 
     => (∀ m' . MonadIO m' => m' String) 
     -> m String 
run2IO' foo = liftIO foo 

... aber ich glaube nicht wirklich, Sie wollen, dass: Sie könnten dann auch schreiben

run2IO' :: MonadIO m => IO String -> m String 
run2IO' foo = liftIO foo 

oder einfach run2IO = liftIO.

+0

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. –

+1

Ä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

+0

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. –

9

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 barmuss 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 barinstanziiert 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.

+0

Es mag oder nicht erwähnenswert sein, die 'PrimBase' Klasse in' primitive' zu ​​erwähnen. – dfeuer