请记住,Haskell 函数是纯函数;给定相同的输入,它们必须始终返回相同的结果。你可以让你的函数返回IO [a]
,这会让你调用newStdGen
,但更好的方法是通过将随机数生成器作为函数的附加参数并在之后返回新的生成器来保持你的代码纯净:
rnd_select :: (Eq a, RandomGen g) => [a] -> Int -> g -> ([a], g)
rnd_select [] _ gen = ([], gen)
rnd_select _ 0 gen = ([], gen)
rnd_select ys n gen =
let (rnd_index, gen') = randomR (1, length ys) gen
(x, xs) = removeAt rnd_index ys
(xs', gen'') = rnd_select xs (n-1) gen'
in (x : xs', gen'')
现在您可以使用它,例如getStdRandom :: (StdGen -> (a, StdGen)) -> IO a
像这样。
> getStdRandom (rnd_select [1..20] 10)
[12,11,14,4,16,7,1,2,18,15]
不过,手动传递生成器可能有点乏味。使这个更整洁的一种方法是使用MonadRandom包。
rnd_select :: (MonadRandom m, Eq a) => [a] -> Int -> m [a]
rnd_select [] _ = return []
rnd_select _ 0 = return []
rnd_select ys n = do
rnd_index <- getRandomR (1, length ys)
let (x, xs) = removeAt rnd_index ys
xs' <- rnd_select xs (n-1)
return (x:xs')
由于IO
是 的实例MonadRandom
,因此您可以直接将其用作IO
操作。
> rnd_select [1..20] 10
[20,18,12,13,5,7,17,9,3,4]
> rnd_select [1..20] 10
[9,18,4,20,6,5,3,15,13,7]
或者您可以使用evalRand
在纯 monad 中运行它,提供您自己的随机数生成器,以便您可以获得可重复的结果(适合调试/测试)。
> evalRand (rnd_select [1..20] 10) (mkStdGen 200)
[4,16,15,13,8,20,6,14,5,3]
> evalRand (rnd_select [1..20] 10) (mkStdGen 200)
[4,16,15,13,8,20,6,14,5,3]