在 Haskell 中生成新的和不同的随机列表(没有 IO)?

Generating new and different random lists in Haskell (without IO)?

这是一个非常新手的问题,虽然我可以找到部分答案,但我仍然难以使整个问题正常工作。我在一个模块中有一组函数,可以处理我设计的特定数据类型。有时我需要创建一个随机值列表。我们可以将我的类型视为多项式,以它们的系数列表的形式给出。所以我应该只需要调用一个 randomPoly 函数(比如说)来在每次调用时生成新列表。为简单起见,所有列表都可以具有相同的长度(比如 4)和相同大小的元素——比如在 0 到 9 之间。

所以我想要的是能够做这样的事情(在 ghci 中):

>>> setRandomSeed 42
>>> randomPoly
[6,5,0,4]
>>> randomPoly
[9,6,3,5]   
 >>> randomPoly
[7,3,9,2]

我当然可以通过在每次需要新列表时将新种子传递给生成器来获得不同的随机列表:

>>> randomPoly st = map (`mod` 10) $ take 4 $ randoms (mkStdGen st) :: [Integer]

但我不想这样做:我想设置一次初始种子,然后让 State 从那时起负责管理生成器的当前值。

在这个阶段,我对 monad、State 和所有其他内容的细节不太感兴趣 - 我正在寻找尽可能接近 "off the shelf" 的解决方案。我只是想要一些我可以使用的东西。大多数例子似乎都非常热衷于教授所有关于 State monad 是如何工作的——这是一个非常光荣的概念——但现在我想要的只是一些快速简单的方法来创建随机列表。

例如,这里有一个愚蠢的函数,它创建随机列表直到所有值的总和为偶数:

mkEvenPoly :: Int -> [Integer]
mkEvenPoly st
  | even $ sum p = p
  | otherwise    = mkEvenPoly $ s+1
  where
    p = map (`mod` 10) $ take 4 $ randoms (mkStdGen st) :: [Integer]

请注意,每次我都需要将一个新的生成值传递给 mkStdGen。我可以用 State monad 做这种事情吗 - 存储生成器的当前值(由 mkStdGen 返回)然后将其用于下一个随机调用?

这是一个 "off the shelf" 解决方案,它使用 MonadRandom 并且非常接近(我认为)您的想法。

我将您的 randomPolymkEvenPoly 函数重写为 return 一个 Rand monad。因此,它们可以在 do 语句中组合成复合 Rand monad 。在生成这个复合 monad 的 do 循环中,随机变量是在没有显式使用生成器的情况下设置的。将单个生成器与复合 monad 一起传递给 runRand 以生成多个随机值。

import Control.Monad.Random

randomPoly :: Rand StdGen [Integer]
randomPoly = map (`mod` 10) <$> take 4 <$> getRandoms

mkEvenPoly :: Rand StdGen [Integer]
mkEvenPoly = do
  p <- randomPoly
  let res
        | even $ sum p = return p
        | otherwise = mkEvenPoly
  res

-- Use do notation to compose multiple monads into one.
randomStuff :: Rand StdGen (Float, [[Integer]])
randomStuff = do
  f <- getRandom
  p0 <- randomPoly
  p1 <- randomPoly
  e0 <- mkEvenPoly
  e1 <- mkEvenPoly
  e2 <- mkEvenPoly
  return (f, [p0, p1, e0, e1, e2])

main = do
  -- set an initial seed once.
  g0 <- getStdGen 

  -- actually generate the random values.
  let (stuff, g1) = runRand randomStuff g0  

  print stuff