6

有人可以描述以下类型构造函数和函数是如何工作的吗?

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]
4

2 回答 2

8

让我们从头开始:

type Rand a = State StdGen a

这一行告诉您这Rand a是一个类型的类型同义词State,其状态由 给出StdGen,其最终值为 type a。这将用于在每个随机数请求之间存储随机数生成器的状态。

的代码getRandom可以转换为 do 表示法:

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

runRand函数采用初始种子nr类型值Rand a(请记住,它只是 的同义词State StdGen a)。它创建一个新的生成器mkStdGen n并将其提供给evalState r. 该函数evalState只计算State s a类型的返回值,忽略状态。

同样,我们可以转换runRandIOdo符号:

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

最后,getRandoms取一个数字n,表示您要生成的随机值的数量。它构建一个列表[1..n]并应用于getRandom该列表。请注意,[1..n]未使用实际值(您可以知道,因为 lambda 函数以 开头\_ -> ...)。该列表只是为了包含正确数量的元素。由于getRandom返回一个单子值,我们使用mapM对列表进行映射,这会导致状态(即StdGen)通过对 的每个调用正确线程化getRandom

于 2012-06-15T09:14:24.263 回答
5

基本思想很简单——要创建伪随机数,您需要在函数调用之间保持一些状态。因此,类型Rand a被定义为“a与随机性所需的状态一起”。

使用Statemonad存储状态。这提供了两个主要的操作——getput,它们完全按照它们听起来的样子。所以getRandom只需查找当前状态,然后调用该random函数。该函数返回两个值:随机值和新状态。然后你只是put新状态并包装结果值。

runRand让您解开给定种子的“随机”值。允许您在给定初始状态的情况下evalState执行有状态计算(即类型的值,State s a或者在本例中为),然后丢弃最终状态,只给您结果。Rand a因此,这使您可以Rand a使用给定的种子运行 a 并仅返回结果值。该值可以只有 type a,而不是Rand a因为它总是会给你相同的种子相同的结果。

runRandomIO只是做同样的事情,只是根据 IO 中的一些全局状态获取种子。

getRandoms只需Rand a通过调用列表中getRandom的每个元素[1..n](忽略实际数字)来获取值列表。

于 2012-06-15T09:17:41.277 回答