计算机是确定性的,不能生成随机数。相反,它们依赖于返回看起来随机的数字分布的数学公式。这些被称为伪随机数生成器。然而,由于确定性,我们有一个问题,如果我们在程序的每次调用期间以相同的方式运行这些公式,我们将得到相同的随机数生成器。显然,这不好,因为我们希望我们的数字是随机的!因此,我们必须为随机生成器提供一个随运行而变化的初始种子值。对于大多数人(即那些不做加密工作的人)来说,随机数生成器是按当前时间播种的。在 Haskell 中,这个伪随机生成器由StdGen
类型表示。这mkStdGen
函数用于创建带有种子的随机数生成器。与 C 中只有一个全局随机数生成器不同,在 Haskell 中,您可以拥有任意数量的随机数,并且可以使用不同的种子创建它们。
但是,有一个警告:由于数字是伪随机的,因此不能保证使用不同种子创建的随机数生成器返回的数字与另一个相比看起来是随机的。这意味着当您调用randomBool
并给它连续的种子值时,不能保证您从创建的数字中获得的数字与带有其后继StdGen
的种子相比是随机的。StdGen
这就是为什么你得到近 50000True
的原因。
为了获得实际上看起来随机的数据,您需要继续使用相同的随机数生成器。如果您注意到,random
Haskell 函数有一个 type StdGen -> (a, StdGen)
。因为 Haskell 是纯的,所以该random
函数接受一个随机数生成器,生成一个伪随机值(返回值的第一个元素),然后返回一个新的StdGen
,它表示以原始种子为种子的生成器,但准备好给出一个新的随机数数字。您需要保留StdGen
它并将其传递给下一个random
函数以获取随机数据。
这是一个示例,生成三个随机布尔值a
、b
和c
。
randomBools :: StdGen -> (Bool, Bool, Bool)
randomBools gen = let (a, gen') = random gen
(b, gen'') = random gen''
(c, gen''') = random gen'''
in (a, b, c)
注意gen
变量是如何通过对 random 的调用“线程化”的。
您可以使用 state monad 来简化传递状态。例如,
import Control.Monad.State
import System.Random
type MyRandomMonad a = State StdGen a
myRandom :: Random a => MyRandomMonad a
myRandom = do
gen <- get -- Get the StdGen state from the monad
let (nextValue, gen') = random gen -- Generate the number, and keep the new StdGen
put gen' -- Update the StdGen in the monad so subsequent calls generate new random numbers
return nextValue
现在您可以将randomBools
函数编写为:
randomBools' :: StdGen -> (Bool, Bool, Bool)
randomBools' gen = fst $ runState doGenerate gen
where doGenerate = do
a <- myRandom
b <- myRandom
c <- myRandom
return (a, b, c)
如果要生成Bool
s 的(有限)列表,可以执行
randomBoolList :: StdGen -> Int -> ([Bool], StdGen)
randomBoolList gen length = runState (replicateM length myRandom) gen
请注意我们如何返回StdGen
作为返回对的第二个元素,以允许将其提供给新函数。
更简单地说,如果您只想从 生成相同类型的随机值的无限列表StdGen
,您可以使用该randoms
函数。这有签名(RandomGen g, Random a) => g -> [a]
。Bool
要生成使用 的起始种子的无限列表x
,您只需运行randoms (mkStdGen x)
. 您可以使用length $ takeWhile id (randoms (mkStdGen x))
. 您应该验证您获得的不同初始值的不同值x
,但如果您提供相同的值,则始终是相同的值x
。
最后,如果你不关心被绑定到IO
monad,Haskell 还提供了一个全局随机数生成器,很像命令式语言。randomIO
在monad 中调用该函数IO
将为您提供您喜欢的任何类型的随机值(只要它是类型类的实例Random
,至少)。myRandom
除了在IO
monad中,您可以像上面一样使用它。这具有额外的便利,它由 Haskell 运行时预播,这意味着您甚至不必担心创建StdGen
. Bool
因此,要在 monad 中创建一个 10 s的随机列表IO
,您所要做的就是replicateM 10 randomIO :: IO [Bool].
希望这可以帮助 :)