如何在Haskell中以隐藏的方式初始化状态(就像PRNG那样)?

Jay*_*Jay 4 monads haskell state-monad

我查看了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
Run Code Online (Sandbox Code Playgroud)

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

*Main> runState getRandom 0
(0,12345)
*Main> runState getRandom 0
(0,12345)
*Main> runState getRandom 1              
(16838,1103527590)
Run Code Online (Sandbox Code Playgroud)

但是我每次打电话都需要把种子传给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
Run Code Online (Sandbox Code Playgroud)

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

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

非常感谢!

new*_*cct 6

randomRIO使用IOmonad.这似乎在解释器中很好用,因为解释器也可以在IOmonad中工作.这就是你在你的例子中所看到的; 你实际上不能在代码的顶级做到这一点 - 无论如何你必须把它放在像所有monad一样的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 for monads中do-expressions语法的一部分.如果您认为需要显式传递状态,那么您没有正确使用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
Run Code Online (Sandbox Code Playgroud)

这是使用ST monad的代码:

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

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

  • 现在我想到了,PRNG必然会使用IO monad,对吗?它需要熵,因此需要来自"某处"的数据.它实际上*需要*至少一个不是引用透明的函数(否则它对于加密代码没有用)!:-) (4认同)