TL; DR. Alle vier dieser Funktionen sind nur Typumwandlungen. Sie sind alle zur Laufzeit nicht betriebsbereit. Die nur Unterschied zwischen ihnen ist die Art Signaturen —, aber es ist die Art Signaturen, die alle Sicherheitsgarantien an erster Stelle erzwingen!
Die ST
Monade und die IO
Monade beide geben Ihnen wandelbar Zustand.
Es ist bekanntlich unmöglich, die IO
Monade zu entkommen. [Nun, nein, Sie können, wenn Sie unsafePerformIO
verwenden. Tun Sie das nicht!] Aus diesem Grund wird die gesamte E/A, die Ihr Programm jemals ausführt, in einem einzigen riesigen IO
Block gebündelt, wodurch eine globale Reihenfolge der Operationen erzwungen wird. [Zumindest bis Sie forkIO
nennen, aber trotzdem ...]
Der Grund unsafePerformIO
so unsicher ist verdammt ist, dass es keine Möglichkeit, wann genau herauszufinden, ob oder wie oft eingeschlossen I/O Operationen passieren —, die in der Regel eine sehr schlechte Sache ist.
Die ST
Monade stellt auch wandelbar Zustand, aber es tut einen Fluchtmechanismus haben — die runST
Funktion. So können Sie aus einem unreinen Wert einen reinen Wert machen. Aber jetzt gibt es keine Möglichkeit zu garantieren, welche Reihenfolge separate ST
Blöcke laufen wird. Um vollständige Zerstörung zu verhindern, müssen wir sicherstellen, dass separate ST
Blöcke nicht miteinander "stören" können.
Aus diesem Grund können Sie in der ST
Monad keine E/A-Operationen ausführen. Sie können auf den veränderbaren Status zugreifen, aber dieser Status darf nicht aus dem Block ST
entfernt werden.
Die IO
Monade und die ST
Monade sind eigentlich das gleiche Monade. Und ein IORef
ist eigentlich ein STRef
, und so weiter. Es wäre also wirklich nützlich, Code schreiben und in beiden Monaden verwenden zu können. Und alle vier von Ihnen genannten Funktionen sind Typumwandlungen, mit denen Sie genau das tun können.
Um die Gefahr zu verstehen, müssen wir verstehen, wie ST
seinen kleinen Trick erreicht. Es ist alles im Phantom s
geben Sie die Typ-Signaturen ein. Um einen ST
Block laufen, muss es für alle möglichen s
arbeiten:
runST :: (forall s. ST s x) -> x
das All wandelbar Zeug hat s
in der Art als auch, und durch einen glücklichen Zufall, das bedeutet, dass jeder Versuch, wandelbar Sachen zurückgeführt der ST
Monade wird schlecht typisiert sein. (Dies ist wirklich ein bisschen ein Hack, aber es funktioniert so perfekt ...)
Zumindest wird es schlecht getippt werden, wenn Sie runST
verwenden. Beachten Sie, dass ioToST
Ihnen eine ST RealWorld x
gibt. Grob gesagt, IO x
& ca. ST RealWorld x
. Aber runST
wird das nicht als Eingabe akzeptieren. Sie können runST
nicht verwenden, um I/O auszuführen.
Die ioToST
gibt Ihnen einen Typ, den Sie nicht mit runST
verwenden können. Aber unsafeIOToST
gibt Ihnen einen Typ, der gut mit runST
funktioniert. An diesem Punkt haben Sie im Grunde implementiert unsafePerformIO
:
unsafePerformIO = runST . ioToST
Die unsafeSTToIO
ermöglicht es Ihnen, wandelbar Sachen aus einem ST
Block und möglicherweise in eine andere zu bekommen:
foobar = do
v <- unsafeSTToIO (newSTRef 42)
let w = runST (readSTRef v)
let x = runST (writeSTRef v 99)
print w
Wanna nehmen ein erraten, was los ist gedruckt werden? Weil die Sache ist, haben wir drei ST
Aktionen hier, die in absolut beliebiger Reihenfolge passieren können. Wird die readSTRef
vor oder nach der writeSTRef
passieren?
[Tatsächlich, in diesem Beispiel, geschieht der Schreibvorgang nie, weil wir nichts mit x
"tun". Aber wenn ich x
zu einem entfernten, nicht verwandten Teil des Codes übergebe, und dieser Code passiert, um es zu inspizieren, macht unsere E/A-Operation plötzlich etwas anderes. Reiner Code sollte nicht in der Lage sein, so wandelbar Sachen beeinflussen]
bearbeiten: Es scheint, ich war etwas verfrüht. Die unsafeSTToIO
Funktion ermöglicht es Ihnen, einen veränderbaren Wert aus der ST
Monade zu nehmen, aber es scheint, es erfordert einen zweiten Anruf an unsafeSTToIO
, um die veränderbare Sache wieder in die die ST
Monad setzen. (An diesem Punkt beide Aktionen sind IO
Aktionen, so dass ihre Bestellung garantiert.)
Sie könnten natürlich Mix in einigen unsafeIOToST
als gut, aber das beweist nicht wirklich, dass unsafeSTToIO
selbst unsicher ist:
foobar = do
v <- unsafeSTToIO (newSTRef 42)
let w = runST (unsafeIOToST $ unsafeSTToIO $ readSTRef v)
let x = runST (unsafeIOToST $ unsafeSTToIO $ writeSTRef v 99)
print w
Ich habe damit herum gespielt, und ich habe es noch nicht geschafft, die Typüberprüfer zu überzeugen, etwas Provokativ unsicher zu machen mit nurunsafeSTToIO
. Ich bin davon überzeugt, dass dies möglich ist, und die verschiedenen Kommentare zu dieser Frage scheinen zu stimmen, aber ich kann kein Beispiel konstruieren. Sie bekommen die Idee zwar; ändere die Typen und deine Sicherheit wird gebrochen.
Der Unterschied liegt in den Typen.ioToST kann nur eine 'ST RealWorld a' erzeugen, die nicht an' runST :: (forall s. ST s a) -> a' übergeben werden kann. – user2407038