运行genRandTupe
时,我会不断获得相同的随机数,但是当以genrandList
作为参数运行gen
时,我每次都会得到一组新的数字。我该如何解决这个问题?为什么没有g = newStdGen
生成一个新的随机数?
import System.Random
import System.IO.Unsafe
type RandTupe = (Int,Int)
genRandTupe :: IO RandTupe
genRandTupe = let [a,b] = genrandList g in return (a,b) where g = newStdGen
genrandList gen = let g = unsafePerformIO gen in take 2 (randomRs (1, 20) g)
gen = newStdGen
答案 0 :(得分:5)
genRandTupe
是constant applicative form。这意味着其let
或where
块中的任何局部变量都会被记忆。通常非常方便!
在您的情况下,这意味着列表[a,b]
仅在整个程序中计算一次。它的计算方式实际上是非法的(不要使用unsafePerformIO
!),但它并不重要,因为它只会发生一次。将此常量元组包含在IO
return
genRandTupe' :: RandTupe
genRandTupe' = let [a,b] = genrandList g in (a,b)
where g = newStdGen
中实际上完全是流畅的,您可以写完
genrandList gen
OTOH,当您在多个不同的地方评估unsafePerformIO
(不是CAF)时,结果不一定会被存储。相反,该函数是重新计算的,使用genRandList
来不安全地修改全局状态(或者可能不是......编译器实际上可以自由地优化它,因为,你知道,{{ 1}}被认为是一个纯函数...)因此每次产生不同的结果。
当然,正确的做法是远离unsafePerformIO
。实际上根本不需要在genRandList
中执行IO,因为它已经接受了一个随机生成器......只是在传递它之前将该生成器绑定到IO:
genRandTupe'' :: IO RandTupe
genRandTupe'' = do
g <- newStdGen
let [a,b] = genrandList g
return (a,b)
randListFrom :: RandomGen g => g -> [Int]
randListFrom g = take 2 (randomRs (1, 20) g)
请注意,因为let [a,b] = ...
现在位于do
块中,所以它现在位于IO
monad中,与genRandTupe''
的CAF闭包分离。