Haskell 随机生成

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]
2个回答

8

让我们从头开始:

type Rand a = State StdGen a

这行代码告诉你,Rand a 是一个类型同义词,它的状态由 StdGen 给出,最终值为类型为 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函数接受一个初始种子 n 和一个类型为Rand a(记住,这只是State StdGen a的同义词)的值r。它使用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

最后,getRandoms 接收一个代表你想要生成的随机数个数的数字 n。它构建一个列表 [1..n],并对列表应用 getRandom。请注意,实际上没有使用 [1..n] 中的值(可以看出来是因为 lambda 函数以 \_ -> ... 开头)。列表只是为了有一个具有正确元素数量的东西而存在。由于 getRandom 返回一个单子值,我们使用 mapM 对列表进行映射,这会导致状态(即 StdGen)在每次调用 getRandom 时正确地穿过。


5
基本思想很简单——要创建伪随机数,需要在函数调用之间维护一些状态。因此,类型Rand a被定义为“a和生成随机数所需的状态”。
使用State monad存储状态。它提供了两个主要操作——getput,它们的功能就像它们的名字一样。因此,getRandom只是查找当前状态,然后调用random函数。该函数返回两个值:随机值和新状态。然后你只需put新状态并包装结果值。 runRand允许您根据种子解包“随机”值。evalState允许您执行具有状态的计算(即类型为State s a或在本例中为Rand a的值),给定初始状态,然后仅丢弃最终状态,只提供结果。因此,这使您可以使用给定的种子运行Rand a并仅返回结果值。该值可以仅具有类型a,而不是Rand a,因为对于相同的种子它将始终给出相同的结果。 runRandomIO执行相同的操作,只是基于IO中的某些全局状态获取种子。 getRandoms通过为[1..n]列表的每个元素(忽略实际数字)调用getRandom来获取Rand a值列表。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接