来自GHC.IO的`ioToST`和`unsafeIOToST`有什么区别?

imz*_*hev 11 io haskell ghc state-monad

GHC.IO中,差异和预期用途有何用途ioToSTunsafeSTToIO定义?

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

-- 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)
Run Code Online (Sandbox Code Playgroud)

Seb*_*edl 12

安全版本必须在IO monad中启动(因为您无法获取ST RealWorldfrom runST)并允许您在IO上下文和ST RealWorld上下文之间切换.它们是安全的,因为ST RealWorld它与IO基本相同.

不安全的版本可以从任何地方开始(因为runST可以在任何地方调用)并允许您在任意ST monad和IO monad之间切换.使用runST纯粹的上下文然后unsafeIOToST在状态monad中进行操作基本上等同于使用unsafePerformIO.

  • 我知道这不是OP的问题,但在我看来,更大的问题是什么,如果有的话,使'unsafeSTToIO`不安全. (4认同)
  • 我在haskell-cafe上发现了一个关于它的帖子.它似乎真的不安全,但我不明白任何一个例子. (2认同)

Mat*_*hid 10

TL; DR.所有这四个函数都只是类型转换.它们在运行时都是无操作的.它们之间的唯一区别是类型签名 - 但它是首先强制执行所有安全保证的类型签名!


ST单子和IO单子都为您提供可变的状态.

逃离IO单子是不可能的.[嗯,不,你可以使用unsafePerformIO.不要这样做!]因此,程序将执行的所有I/O都捆绑到一个巨大的IO块中,从而强制执行操作的全局排序.[至少,直到你打电话forkIO,但无论如何...]

原因unsafePerformIO是如此不安全的是,没有办法弄清楚封闭的I/O操作何时,是否或多少次 - 这通常是一件非常糟糕的事情.

ST单子还提供可变的状态,但它确实有一个逃生机制- runST功能.这使您可以将不纯的值转换为纯值.但现在无法保证单独ST块的运行顺序.为了防止完全破坏,我们需要确保单独的ST块不能相互"干扰".

因此,您无法在STmonad中执行任何I/O操作.您可以访问可变状态,但不允许该状态转义ST块.

IO单子和ST单子实际上是相同的单子.并且IORef实际上是一个STRef,依此类推.因此,能够编写代码并在两个monad中使用它真的非常有用.你提到的所有四个函数都是类型转换,可以让你做到这一点.

要了解危险,我们需要了解如何ST实现它的小技巧.它都是s类型签名中的幻像类型.要运行一个ST块,它需要为所有可能的方法工作s:

runST :: (forall s. ST s x) -> x
Run Code Online (Sandbox Code Playgroud)

所有可变的东西都有s类型,并且由于一个快乐的意外,这意味着任何将可变东西从STmonad中返回的尝试都是错误的.(这真的是一个黑客,但它的工作非常完美...)

至少,如果你使用它将是错误的类型runST.请注意,ioToST给你一个ST RealWorld x.粗略地说,IO xST RealWorld x.但runST不会接受这一点作为输入.所以你不能runST用来运行I/O.

ioToST给你,你可以不使用类型runST.但是unsafeIOToST给你一个可以正常工作的类型runST.那时,你基本上已经实现了unsafePerformIO:

unsafePerformIO = runST . ioToST
Run Code Online (Sandbox Code Playgroud)

unsafeSTToIO让你获得可变的东西出来一个ST块,并有可能进入另一个:

foobar = do
  v <- unsafeSTToIO (newSTRef 42)
  let w = runST (readSTRef v)
  let x = runST (writeSTRef v 99)
  print w
Run Code Online (Sandbox Code Playgroud)

想猜一下打印什么?因为事情是,我们在ST这里有三个动作,它们可以完全按任何顺序发生.会readSTRef发生在之前还是之后writeSTRef

[实际上,在这个例子中,写入永远不会发生,因为我们不会"做"任何事情x.但是如果我传递x给代码中一些遥远的,无关的部分,并且代码碰巧检查它,那么突然我们的I/O操作会做出不同的事情.纯代码不应该影响那样的可变东西!]


编辑:看来我有点不成熟.该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
Run Code Online (Sandbox Code Playgroud)

我打得四处这一点,我还没有成功地说服类型检查,让我用做一些可证明不安全 unsafeSTToIO.我仍然相信它可以做到,关于这个问题的各种评论似乎都同意,但我实际上无法构建一个例子.你明白了; 改变类型,你的安全被打破.

  • "因此,能够编写代码并在两个monad中使用它真的非常有用." 实际上有一种非常干净的方法来实现这个目的而没有任何不安全的东西:[`primitive`](https://hackage.haskell.org/package/primitive)提供了一个带有`ST s`和`IO`实例的`PrimMonad`类几乎所有你想写的函数都可以处理任何一个实例.这些转换仅用于处理许多库函数(不必要地)特定的事实. (3认同)
  • 我相信`foobar`的例子是行不通的.原因是`runST`需要一个polytyped值,而`v`是montyped.实际上,`unsafeSTToIO(newSTRef 42)`有类型`forall s.IO(STRef s Int)`,而不是`IO(forall s.STRef s Int)`. (2认同)