Ответ 2
TL; DR. Все четыре из этих функций - это просто приемы. Все они не работают во время выполнения. Единственная разница между ними - это типовые подписи — но это сигнатуры типов, которые в первую очередь обеспечивают все гарантии безопасности!
Монада ST
и монада IO
обе дают вам изменяемое состояние.
Лично невозможно избежать монады IO
. [Ну, нет, вы можете, если используете unsafePerformIO
. Не делайте этого!] Из-за этого все операции ввода/вывода, которые ваша программа когда-либо будет выполнять, объединяются в один гигантский блок IO
, тем самым обеспечивая глобальный порядок операций. [По крайней мере, пока вы не назовете forkIO
, но в любом случае...]
Причина unsafePerformIO
настолько проклята, что она небезопасна, так это то, что невозможно точно определить, когда, если или сколько раз будут выполняться вложенные операции ввода-вывода — что обычно очень плохо.
Монада ST
также предоставляет изменчивое состояние, но у нее есть механизм-побег — runST
. Это позволяет превратить нечистую ценность в чистую. Но теперь нет способа гарантировать, в каком порядке будут выполняться отдельные блоки ST
. Чтобы предотвратить полное разрушение, нам нужно убедиться, что отдельные блоки ST
не могут "вмешиваться" друг в друга.
По этой причине вы не можете выполнять какие-либо операции ввода-вывода в монаде ST
. Вы можете получить доступ к изменяемому состоянию, но этому состоянию не разрешено покидать блок ST
.
Монада IO
и монада ST
на самом деле являются одной и той же монадой. И IORef
на самом деле является STRef
и так далее. Таким образом, было бы действительно полезно иметь возможность писать код и использовать его в обеих монадах. И все четыре функции, которые вы упомянули, - это типы, которые позволяют делать именно это.
Чтобы понять опасность, нам нужно понять, как ST
достигает этого небольшого трюка. Все это в типе phantom s
в типах подписи. Чтобы запустить блок ST
, он должен работать для всех возможных s
:
runST :: (forall s. ST s x) -> x
Все изменчивые вещи имеют тип s
в этом типе, и счастливой случайностью это означает, что любая попытка вернуть изменчивый материал из монады ST
будет плохо напечатана. (Это действительно немного взломать, но он отлично работает...)
По крайней мере, он будет неправильно введен, если вы используете runST
. Обратите внимание, что ioToST
дает вам ST RealWorld x
. Грубо говоря, IO x
& approx; ST RealWorld x
. Но runST
не будет принимать это как вход. Поэтому вы не можете использовать runST
для запуска ввода-вывода.
ioToST
предоставляет тип, который нельзя использовать с runST
. Но unsafeIOToST
дает вам тип, который отлично работает с runST
. В этот момент вы в основном реализовали unsafePerformIO
:
unsafePerformIO = runST . ioToST
unsafeSTToIO
позволяет вам получать изменяемые данные из одного блока ST
и потенциально в другое:
foobar = do
v <- unsafeSTToIO (newSTRef 42)
let w = runST (readSTRef v)
let x = runST (writeSTRef v 99)
print w
Хотите угадать, что будет печататься? Потому что дело в том, что у нас есть три ST
действия, которые могут произойти в абсолютно любом порядке. Будет ли readSTRef
происходить до или после writeSTRef
?
[На самом деле, в этом примере запись никогда не происходит, потому что мы ничего не делаем с x
. Но если я передаю x
какой-то отдаленной, не связанной части кода, и этот код будет проверять его, внезапно наша операция ввода-вывода сделает что-то другое. Чистый код не должен влиять на изменяемые вещи, подобные этому!]
Изменить: Похоже, я был немного преждевременным. Функция unsafeSTToIO
позволяет вывести изменяемое значение из монады ST
, но, похоже, для второго обращения к unsafeSTToIO
требуется повторная перестановка изменчивой вещи в монаду ST
. (В этот момент оба действия являются действиями IO
, поэтому их порядок гарантирован.)
Вы могли бы, конечно, смешиваться и с некоторыми unsafeIOToST
, но это на самом деле не доказывает, что unsafeSTToIO
сам по себе небезопасен:
foobar = do
v <- unsafeSTToIO (newSTRef 42)
let w = runST (unsafeIOToST $ unsafeSTToIO $ readSTRef v)
let x = runST (unsafeIOToST $ unsafeSTToIO $ writeSTRef v 99)
print w
Я играл с этим, и мне еще не удалось убедить проверку типа, чтобы я мог сделать что-то явно небезопасное, используя только unsafeSTToIO
. Я по-прежнему убежден, что это можно сделать, и различные комментарии по этому вопросу, похоже, согласны, но я не могу построить пример. Вы получаете идею, хотя; измените типы, и ваша безопасность будет нарушена.