Raf*_*ini 18 random haskell functional-programming referential-transparency
我需要用于模拟的小型高斯随机数列表,所以我尝试了以下方法:
import System.Random
seed = 10101
gen = mkStdGen seed
boxMuller mu sigma (r1,r2) = mu + sigma * sqrt (-2 * log r1) * cos (2 * pi * r2)
Run Code Online (Sandbox Code Playgroud)
这只是Box-Muller算法 - 给定r1,r2在[0,1]区间内的均匀随机数,它返回一个高斯随机数.
normals 0 g = []
normals n g = take n $ map (boxMuller 0 1) $ pairs $ randoms g
where pairs (x:y:zs) = (x,y):(pairs zs)
Run Code Online (Sandbox Code Playgroud)
所以我normals
每次需要我的随机数列表时都会使用这个函数.
问题必须明显:它始终生成相同的序列,因为我总是使用相同的种子!我没有得到新的序列,我只是一直得到序列的前n个值.
我明确假装的是,当我输入时:
x = normal 10
y = normal 50
Run Code Online (Sandbox Code Playgroud)
我要将x作为前10个值,将map (boxMuller 0 1) $ pairs $ randoms g
y作为此列表中的下50个值,依此类推.
当然这是不可能的,因为在给定相同输入的情况下,函数必须始终返回相同的值.我如何逃脱这个陷阱?
jro*_*way 28
我认为你的计算需要在monad中抽象生成器的随机数是最干净的.这是代码的样子:
我们将把StdGen实例放在状态monad中,然后在状态monad的get和set方法上提供一些糖来给我们随机数字.
首先,加载模块:
import Control.Monad.State (State, evalState, get, put)
import System.Random (StdGen, mkStdGen, random)
import Control.Applicative ((<$>))
Run Code Online (Sandbox Code Playgroud)
(通常我可能不会指定导入,但是这样可以很容易地理解每个函数的来源.)
然后我们将定义我们的"需要随机数的计算"monad; 在这种情况下,State StdGen
被调用的别名R
.(因为"随机"和"兰德"已经意味着别的东西.)
type R a = State StdGen a
Run Code Online (Sandbox Code Playgroud)
R的工作方式是:定义一个需要随机数流(monadic"action")的计算,然后用"运行它" runRandom
:
runRandom :: R a -> Int -> a
runRandom action seed = evalState action $ mkStdGen seed
Run Code Online (Sandbox Code Playgroud)
这将采取操作和种子,并返回操作的结果.就像往常一样evalState
,runReader
等等.
现在我们只需要州立大学附近的糖.我们get
用来获取StdGen,我们put
用来安装一个新状态.这意味着,为了得到一个随机数,我们会写:
rand :: R Double
rand = do
gen <- get
let (r, gen') = random gen
put gen'
return r
Run Code Online (Sandbox Code Playgroud)
我们得到随机数生成器的当前状态,用它来获取一个新的随机数和一个新的生成器,保存随机数,安装新的生成器状态,并返回随机数.
这是一个可以使用runRandom运行的"动作",所以让我们试试吧:
ghci> runRandom rand 42
0.11040701265689151
it :: Double
Run Code Online (Sandbox Code Playgroud)
这是一个纯函数,因此如果使用相同的参数再次运行它,您将得到相同的结果.杂质留在你传递给runRandom的"动作"中.
无论如何,你的函数想要成对的随机数,所以让我们写一个动作来产生一对随机数:
randPair :: R (Double, Double)
randPair = do
x <- rand
y <- rand
return (x,y)
Run Code Online (Sandbox Code Playgroud)
使用runRandom运行它,你会看到对中有两个不同的数字,正如你所期望的那样.但请注意,您不必提供带有参数的"rand"; 这是因为函数是纯粹的,但"rand"是一个动作,它不一定是纯粹的.
现在我们可以实现你的normals
功能了. boxMuller
正如你在上面定义的那样,我只是添加了一个类型签名,这样我就可以理解发生了什么,而不必阅读整个函数:
boxMuller :: Double -> Double -> (Double, Double) -> Double
boxMuller mu sigma (r1,r2) = mu + sigma * sqrt (-2 * log r1) * cos (2 * pi * r2)
Run Code Online (Sandbox Code Playgroud)
在实现了所有辅助函数/动作后,我们最终可以编写normals
一个0参数的动作,它返回一个(懒惰生成的)无限正常分布的双精度列表:
normals :: R [Double]
normals = mapM (\_ -> boxMuller 0 1 <$> randPair) $ repeat ()
Run Code Online (Sandbox Code Playgroud)
如果你想要,你也可以不那么简洁地写这个:
oneNormal :: R Double
oneNormal = do
pair <- randPair
return $ boxMuller 0 1 pair
normals :: R [Double]
normals = mapM (\_ -> oneNormal) $ repeat ()
Run Code Online (Sandbox Code Playgroud)
repeat ()
给monadic动作一个无限的无任何流来调用函数(并且是法线结果无限长的原因).我最初写过[1..]
,但我重新编写它以删除1
程序文本中的无意义.我们不是在整数上运行,我们只想要一个无限的列表.
无论如何,就是这样.要在真实程序中使用它,您只需在R动作中执行需要随机法线的工作:
someNormals :: Int -> R [Double]
someNormals x = liftM (take x) normals
myAlgorithm :: R [Bool]
myAlgorithm = do
xs <- someNormals 10
ys <- someNormals 10
let xys = zip xs ys
return $ uncurry (<) <$> xys
runRandom myAlgorithm 42
Run Code Online (Sandbox Code Playgroud)
编程monadic动作的常用技术适用; 保持尽可能少的应用程序R
,事情不会太乱.
哦,还有其他的事情:懒惰可以干净地"泄漏"在monad边界之外.这意味着你可以写:
take 10 $ runRandom normals 42
Run Code Online (Sandbox Code Playgroud)
它会起作用.
你得到的列表randoms
是无限的,当你使用有限前缀时,你不需要扔掉无限的尾巴.您可以将随机数作为附加参数传递,并将未使用的随机数作为附加结果返回,或者您可以将无限序列的随机数存储在状态monad中.
编译器和其他需要提供唯一符号的代码也会出现类似的问题.这只是Haskell真正的痛苦,因为你在整个代码中处理状态(随机数生成器或符号生成器).
我已经完成了具有显式参数和monad的随机算法,并且没有一个真正令人满意.如果你是grok monads,我可能会建议使用一个包含尚未使用的无限随机数列表的状态monad.