В чем разница между `ioToST` и` unsafeIOToST` от GHC.IO

Каковы могут быть различия и предполагаемые применения для ioToST и unsafeSTToIO, определенных в GHC.IO?

-- ---------------------------------------------------------------------------

-- Coercions between IO and ST

-- | A monad transformer embedding strict state transformers in the 'IO'
-- monad.  The 'RealWorld' parameter indicates that the internal state
-- used by the 'ST' computation is a special one supplied by the 'IO'
-- monad, and thus distinct from those used by invocations of 'runST'.
stToIO        :: ST RealWorld a -> IO a
stToIO (ST m) = IO m

ioToST        :: IO a -> ST RealWorld a
ioToST (IO m) = (ST m)

-- This relies on IO and ST having the same representation modulo the
-- constraint on the type of the state
--
unsafeIOToST        :: IO a -> ST s a
unsafeIOToST (IO io) = ST $ \ s -> (unsafeCoerce# io) s

unsafeSTToIO :: ST s a -> IO a
unsafeSTToIO (ST m) = IO (unsafeCoerce# m)

Ответ 1

Безопасные версии должны запускаться в монаде IO (потому что вы не можете получить ST RealWorld от runST) и разрешить вам переключаться между контекстом ввода-вывода и контекстом ST RealWorld. Они безопасны, потому что ST RealWorld в основном то же, что и IO.

Небезопасные версии могут запускаться в любом месте (потому что runST можно вызывать в любом месте) и позволяют переключаться между произвольной ST-монадой ST и монадой ввода-вывода. Использование runST из чистого контекста, а затем выполнение unsafeIOToST внутри государственной монады в основном эквивалентно использованию unsafePerformIO.

Ответ 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. Я по-прежнему убежден, что это можно сделать, и различные комментарии по этому вопросу, похоже, согласны, но я не могу построить пример. Вы получаете идею, хотя; измените типы, и ваша безопасность будет нарушена.