有人可以描述以下类型构造函数和函数如何工作?
type Rand a = State StdGen a
getRandom :: (Random a) => Rand a
getRandom = get >>= (\r -> let (a,g) = random r in (put g) >> (return a))
runRand :: Int -> Rand a -> a
runRand n r = evalState r $ mkStdGen n
runRandIO :: Rand a -> IO a
runRandIO r = randomIO >>= (\rnd -> return $ runRand rnd r)
getRandoms :: (Random a) => Int -> Rand [a]
getRandoms n = mapM (\_ -> getRandom) [1..n]
Run Code Online (Sandbox Code Playgroud)
让我们从头开始:
type Rand a = State StdGen a
Run Code Online (Sandbox Code Playgroud)
此行告诉您这Rand a是类型的类型同义词State,其状态由其给定StdGen,其最终值为type a.这将用于在每个随机数请求之间存储随机数生成器的状态.
代码getRandom可以转换为符号:
getRandom :: (Random a) => Rand a
getRandom = do
r <- get -- get the current state of the generator
let (a,g) = random r in do -- call the function random :: StdGen -> (a, StdGen)
put g -- store the new state of the generator
return a -- return the random number that was generated
Run Code Online (Sandbox Code Playgroud)
该runRand函数采用初始种子n和r类型值Rand a(记住,它只是一个同义词State StdGen a).它创建了一个新的生成器mkStdGen n并将其提供给它evalState r.该函数evalState只是计算State s a类型的返回值,忽略状态.
同样,我们可以转换runRandIO为do符号:
runRandIO :: Rand a -> IO a
runRandIO r = do
rnd <- randomIO -- generate a new random number using randomIO
return (runRand rnd r) -- use that number as the initial seed for runRand
Run Code Online (Sandbox Code Playgroud)
最后,使用getRandoms一个数字n表示要生成的随机值的数量.它构建一个列表[1..n]并应用于getRandom列表.请注意,[1..n]未使用实际值(您可以告诉因为lambda函数以\_ -> ...)开头.列表就是要有正确数量的元素.由于getRandom返回monadic值,我们使用mapM映射列表,这导致状态(即StdGen)通过每个调用正确地进行了线程化getRandom.
基本思路很简单 - 要创建伪随机数,您需要在函数调用之间保持一些状态.因此,类型Rand a被定义为" a与随机性所需的状态一起".
使用Statemonad存储状态.这提供了两个主要的动作 - get并且put,它们完全听起来像它们.所以getRandom只需查找当前状态然后调用该random函数.此函数返回两个值:随机值和新状态.然后你只是put新的状态并包装结果值.
runRand让你打开给定种子的"随机"值.evalState允许您State s a在Rand a给定初始状态的情况下执行有状态计算(即,类型的值,或者,在这种情况下),然后只丢弃最终状态,仅给出结果.因此,这允许您Rand a使用给定的种子运行a 并仅返回结果值.值可以只有类型a,而不是Rand a因为它总是会为同一个种子提供相同的结果.
runRandomIO 只是做同样的事情,除了在IO中获得基于某个全局状态的种子.
getRandoms只需Rand a通过调用列表的getRandom每个元素[1..n](忽略实际数字)来获取值列表.