如何在Haskell中以隐藏的方式初始化状态(就像PRNG一样)?
我看了一些关于国家单子的教程,我想我明白了 例如,例如: 好的,我可以使用getRandom:如何在Haskell中以隐藏的方式初始化状态(就像PRNG一样)?,haskell,monads,state-monad,Haskell,Monads,State Monad,我看了一些关于国家单子的教程,我想我明白了 例如,例如: 好的,我可以使用getRandom: *Main> runState getRandom 0 (0,12345) *Main> runState getRandom 0 (0,12345) *Main> runState getRandom 1 (16838,1103527590) 但每次我打电话给PRNG时,我仍然需要把种子传给它。我知道 Haskell实施中可用的PRNG不需要: Pre
*Main> runState getRandom 0
(0,12345)
*Main> runState getRandom 0
(0,12345)
*Main> runState getRandom 1
(16838,1103527590)
但每次我打电话给PRNG时,我仍然需要把种子传给它。我知道
Haskell实施中可用的PRNG不需要:
Prelude> :module Random
Prelude Random> randomRIO (1,6 :: Int)
(...) -- GHC prints some stuff here
6
Prelude Random> randomRIO (1,6 :: Int)
1
所以我可能误解了国家单子,因为我在大多数教程中都能看到
似乎不是“持久”状态,只是一种方便的线程状态方式
所以。。。如何使状态自动初始化(可能是从某些
使用时间和其他不太可预测的数据(如随机模块)的函数
是吗
非常感谢
secretStateValue :: IORef SomeType
secretStateValue = unsafePerformIO $ newIORef initialState
{-# NOINLINE secretStateValue #-}
现在,在IO monad中使用normal访问secretStateValue
所以我可能误解了State monad,因为我在大多数教程中看到的似乎不是“持久”状态,而是线程状态的一种便捷方式
状态monad正是关于在某个范围内线程化状态的
如果您想要顶级状态,那就在语言之外(并且您必须使用全局可变变量)。请注意,这可能会使代码的线程安全性变得复杂——该状态是如何初始化的?什么时候?通过哪个线程?
randomRIO
使用IO
monad。这似乎在解释器中工作得很好,因为解释器也在IO
monad中工作。这就是你在例子中看到的;实际上,您不能在代码的顶层这样做——您必须像所有单子一样将其放入do表达式中
在一般代码中,您应该避免IO monad,因为一旦您的代码使用IO monad,它将永远与外部状态绑定在一起——您无法摆脱它(即,如果您有使用IO monad的代码,任何调用它的代码也必须使用IO monad;没有安全的方法“摆脱”它)。因此,IO
monad应该只用于访问外部环境等绝对需要的事情
对于像本地自包含状态这样的事情,您不应该使用IO monad。您可以使用前面提到的State
monad,也可以使用ST
monad。ST单子包含许多与IO单子相同的特性;i、 e.有STRef
可变细胞,类似于IORef
。与IO相比,ST的优点在于,当您完成时,您可以调用ST monad上的runST
,从monad中获得计算结果,这是IO无法做到的
至于“隐藏”状态,这只是Haskell for Monad中do表达式语法的一部分。如果您认为需要显式传递状态,那么您没有正确使用monad语法
以下是在IO Monad中使用IORef的代码:
import Data.IORef
foo :: IO Int -- this is stuck in the IO monad forever
foo = do x <- newIORef 1
modifyIORef x (+ 2)
readIORef x
-- foo is an IO computation that returns 3
import Data.IORef
foo::IO Int——这永远卡在IO monad中
foo=do x说到它,Random
使用atomicModifyIORef
而不是readIORef
+writeIORef
对;这样做似乎是一个好主意。事实上,如果使用atomicModifyIORef从多个线程进行访问,这是一个更好的主意。谢谢!这很有帮助。关于你的评论,“在前者中,我们不能不将其放入另一个IO计算中”——我支持这就是为什么当你组合多个单体(使用单体转换器)时,IO单体需要位于最里面的原因,那么?现在我想起来了,PRNG必然会使用IO单体,对吗?它需要熵,所以它需要来自“某处”的数据。实际上,它至少需要一个引用不透明的函数(否则它就不会有用,例如,对于加密代码)!:-)
import Data.IORef
foo :: IO Int -- this is stuck in the IO monad forever
foo = do x <- newIORef 1
modifyIORef x (+ 2)
readIORef x
-- foo is an IO computation that returns 3
import Control.Monad.ST
import Data.STRef
bar :: Int
bar = runST (do x <- newSTRef 1
modifySTRef x (+ 2)
readSTRef x)
-- bar == 3