问题描述
假设我有一个这样的列表:
Suppose that I have a list like this:
let list = ["random", "foo", "random", "bar", "random", "boo"]
我想遍历一个列表并将所有随机"元素映射到不同的随机字符串:
I want to iterate over a list and map all "random" elements to different random strings:
let newList = fmap randomize list
print newList
-- ["dasidias", "foo", "gasekir", "bar", "nabblip", "boo"]
我的随机函数如下所示:
My randomize function looks like this:
randomize :: String -> String
randomize str =
case str of
"random" -> randStr
_ -> str
where
randStr = take 10 $ randomRs ('a','z') $ unsafePerformIO newStdGen
但我为每个随机"元素得到相同的随机字符串:
But I get the same random string for every "random" element:
["abshasb", "foo", "abshasb", "bar", "abshasb", "boo"]
我无法弄清楚为什么会发生这种情况以及如何为每次出现的随机"获得不同的随机值.
I can't figure out why is this happening and how to get a different random value for each occurrence of "random".
推荐答案
你的代码有两个问题:
- 您正在调用
unsafePerformIO
,但明确违反了该函数的约定.你有责任证明你提供给unsafePerformIO
的东西实际上是纯粹的,并且编译器在它的权利范围内表现得好像是这种情况,而这里绝对不是. - 您在使用后没有仔细跟踪更新后的随机数生成器状态.实际上,使用
randomRs
不可能正确地做到这一点;如果您使用randomRs
,那么对于第一个近似值,这必须是您的程序所需的最后随机性.
- You are calling
unsafePerformIO
, but explicitly violating the contract of that function. It is on you to prove that the thing you provide tounsafePerformIO
is actually pure, and the compiler is within its rights to act as if that's the case, and here it is definitely not. - You are not carefully tracking the updated random number generator state after using it. Indeed, it is not possible to do this correctly with
randomRs
; if you userandomRs
, then to a first approximation, that must be the last randomness your program needs.
解决这两个问题的最简单方法是承认您确实在做IO
.所以:
The simplest fix to both of these is to admit that you really, truly are doing IO
. So:
import Control.Monad
import System.Random
randomize :: String -> IO String
randomize "random" = replicateM 10 (randomRIO ('a', 'z'))
randomize other = pure other
在 ghci 中试用:
Try it out in ghci:
> traverse randomize ["random", "foo", "random", "bar", "random", "boo"]
["xytuowzanb","foo","lzhasynexf","bar","dceuvoxkyh","boo"]
没有调用unsafePerformIO
,因此没有推卸的证明责任;并且 randomRIO
在隐藏的 IORef
中为您跟踪更新的生成器状态,因此您可以在每次调用时正确地继续推进它.
There is no call to unsafePerformIO
, and so no proof burden to shirk; and randomRIO
tracks the updated generator state for you in a hidden IORef
, and so you correctly continue advancing it on each call.
这篇关于如何在 Haskell 中生成不同的随机值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!