4

我浏览了一些关于 State monad 的教程,我想我明白了。

例如,在这个不错的教程中:

import Data.Word

type LCGState = Word32

lcg :: LCGState -> (Integer, LCGState)
lcg s0 = (output, s1) 
  where s1 = 1103515245 * s0 + 12345
        output = fromIntegral s1 * 2^16 `div` 2^32


getRandom :: State LCGState Integer
getRandom = get >>= \s0 -> let (x,s1) = lcg s0
                           in put s1 >> return x

好的,所以我可以使用 getRandom:

*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

所以我可能误解了 State monad,因为我在大多数教程中看到的似乎不是“持久”状态,而只是一种方便的线程状态方式。

那么......我怎样才能拥有自动初始化的状态(可能来自使用时间和其他不可预测数据的某些函数),就像 Random 模块一样?

非常感谢!

4

3 回答 3

6

randomRIO使用IO单子。这似乎在解释器中工作得很好,因为解释器也在IOmonad 中工作。这就是您在示例中看到的内容;你实际上不能在代码的顶层做到这一点——你必须像所有的单子一样把它放在一个 do-expression 中。

在一般代码中,您应该避免使用 IO monad,因为一旦您的代码使用 IO monad,它就永远与外部状态相关联——您无法摆脱它(即,如果您有使用 IO monad 的代码,任何代码调用它也必须使用 IO monad;没有安全的方法可以“摆脱”它)。所以IOmonad 应该只用于诸如访问外部环境之类的事情,这是绝对需要的事情。

对于像本地独立状态这样的事情,你不应该使用 IO monad。您可以使用State前面提到的 monad,也可以使用STmonad。ST monad 包含许多与 IO monad 相同的特性;即存在STRef可变单元格,类似于IORef. 与 IO 相比,ST 的好处在于,当你完成后,你可以调用runSTST monad 从 monad 中获取计算结果,而这是 IO 无法做到的。

至于“隐藏”状态,这只是 Haskell 中用于单子的 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

这是使用 ST monad 的代码:

import Control.Monad.ST
import Data.STRef
bar :: Int
bar = runST (do x <- newSTRef 1
                modifySTRef x (+ 2)
                readSTRef x)
-- bar == 3

代码的简单性本质上是一样的;除了在后一种情况下,我们可以从 monad 中获取值,而在前一种情况下,我们不能不将其放入另一个 IO 计算中。

于 2009-07-13T21:12:16.697 回答
4
secretStateValue :: IORef SomeType
secretStateValue = unsafePerformIO $ newIORef initialState
{-# NOINLINE secretStateValue #-}

现在在 IO monad 中使用普通的readIORef 和 writeIORef访问您的 secretStateValue 。

于 2009-07-13T18:50:26.670 回答
4

所以我可能误解了 State monad,因为我在大多数教程中看到的似乎不是“持久”状态,而只是一种方便的线程状态方式。

状态单子正是关于通过某个范围的线程状态。

如果您想要顶级状态,那是在语言之外(并且您必须使用全局可变变量)。请注意这可能会使您的代码的线程安全变得复杂——该状态是如何初始化的?什么时候?并通过哪个线程?

于 2009-07-13T20:08:43.843 回答