我试图在 Haskell 中获得一个随机数。(我目前正在学习,还没有进入 Monads 或 IO 等)问题是 System.Random 中的函数都返回一个 IO Int,然后我不能在我使用的其余代码中使用它整数和浮点数。
这里的目标是从列表中选择一对,其中第一个是代表概率的浮点数。所以我的计划是使用一个随机数根据其概率选择一对。
这是新 Haskell 程序员的常见障碍。你想逃避 IO,需要一段时间才能找到最好的方法。Learn You A Haskell 教程很好地解释了使用 state monad 生成随机数的一种方法,但它仍然必须使用getStdGen
or来播种newStdGen
,它们在 IO 中。
对于简单的情况,你可以做类似的事情
myPureFunction :: Float -> Float
myPureFunction x = 2 * x
main :: IO ()
main = do
-- num :: Float
num <- randomIO :: IO Float
-- This "extracts" the float from IO Float and binds it to the name num
print $ myPureFunction num
所以你看,你可以得到你的随机数main
,然后将这个值传递给一个执行处理的纯函数。
你可能会问自己为什么要在 Haskell 中生成随机数。有很多很好的理由,其中大部分与类型系统有关。由于生成随机数需要在操作系统中修改 StdGen 的状态,所以它必须存在于内部IO
,否则您可能会有一个纯函数,每次都会给您不同的结果。
想象一下这个人为的场景:
myConstant :: Int
myConstant = unsafePerformIO randomIO
blowUpTheWorld :: IO ()
blowUpTheWorld = error "Firing all the nukes"
main :: IO ()
main = do
if even myConstant
then print "myConstant is even"
else blowUpTheWorld
如果你跑了几次,很可能你最终会“发射所有的核武器”。显然,这很糟糕。 myConstant
应该是恒定的,但是每次运行程序时都会得到不同的值。Haskell 希望保证纯函数在给定相同输入的情况下总是返回相同的值。
现在可能很烦人,但它是函数式程序员工具包中的一个强大工具。
这里有很好的答案,但我觉得更完整的答案会非常简单地展示如何在 Haskell 中获取和使用随机数,这对命令式程序员来说是有意义的。
首先,您需要一个随机种子:
import System.Random
newRand = randomIO :: IO Int
因为newRand
is 的 typeIO Int
而不是 not Int
,所以它不能用作函数参数。(这将 Haskell 函数保留为纯函数,在相同的输入上总是返回相同的结果。)
但是,我们可以简单地输入newRand
GHCI 并每次获得一个唯一的随机种子。这仅是可能的,因为newRand
它是类型IO
并且不是标准(不可变)变量或函数。
*Main> newRand
-958036805781772734
然后,我们可以将此种子值复制并粘贴到为我们创建随机数列表的函数中。如果我们定义如下函数:
randomList :: Int -> [Double]
randomList seed = randoms (mkStdGen seed) :: [Double]
当函数在 GHCI 中运行时,粘贴给定的种子:
*Main> take 10 randomList (-958036805781772734)
[0.3173710114340238,0.9038063995872138,0.26811089937893495,0.2091390866782773,0.6351036926797997,0.7343088946561198,0.7964520135357062,0.7536521528870826,0.4695927477527754,0.2940288797844678]
注意我们如何从 0 到 1(不包括)得到熟悉的值。我们不像在命令式语言中那样每次迭代都生成一个新的随机数,而是提前生成一个随机数列表,并在每次连续递归时使用列表尾部的头部。一个例子:
pythagCheck :: [Double] -> [Double] -> [Int]
pythagCheck (x:xs) (y:ys)
| (a^2) + (b^2) == (c^2) = [a, b, c]
| otherwise = pythagCheck xs ys
where aplusb = ceiling (x * 666)
a = ceiling (y * (fromIntegral (aplusb - 1)))
b = aplusb - a
c = 1000 - a - b
提前创建两个列表并将它们作为参数输入,这样我们就可以搜索(一个也是唯一的!)毕达哥拉斯三元组,其中 a + b + c = 1000。当然,您希望为每个列表使用不同的随机种子列表:
*Main> newRand
3869386208656114178
*Main> newRand
-5497233178519884041
*Main> list1 = randomList 3869386208656114178
*Main> list2 = randomList (-5497233178519884041)
*Main> pythagCheck list1 list2
[200,375,425]
如前所述,随机数不能真正是纯值1。
但是,这并不需要打扰您。换个角度看:其他语言根本没有纯值之类的东西,它总是带有你正在处理的现实世界干扰的状态。IO
Haskell 也可以在monad中做到这一点。你不需要知道它是如何工作的,只需模仿它在程序语言中的样子(不过这里有一些陷阱)。
首先你需要一些算法,它与语言没有任何关系。显而易见的方法是在列表中累积概率,并将生成的阶跃函数用作从 [0, 1[ 到所需值的映射。
probsListLookup :: [(Double, a)] -> Double -> a
probsListLookup pAssoc = look acc'dList
where acc'dList = scanl1 (\(pa,_) (pn,x) -> (pa+pn,x)) pAssoc
look ((pa, x) : pas) rval
| rval < pa = look pas rval
| otherwise = x
请注意,这既不能很好地处理无效输入(概率总和不等于 1 等),也不能有效地为每个请求的值2加扰O ( n ) 。更重要的是,请注意它是一个纯函数!尽可能多地使用纯函数通常是一个好主意,并且仅在绝对必要时才使用。就像现在:我们需要获得0 到 1 之间的单个值。简单!acc'dList
IO
Double
main = do
lookupVal <- randomRIO (0, 1)
print $ probsListLookup [(0.1, 1), (0.2, 2), (0.3, 4), (0.4, 5)] lookupVal
1至少不是像Int
; 不过,您实际上可以对整个概率分布进行“纯计算” 。明确地这样做非常麻烦,但是 Haskell 允许您使用特定的 monad s(或实际上是 comonads)使其与在 Haskell IO(或任何不纯语言)中一样简单,但没有输入/输出的危险。
2您可以使用Data.Map
.
我不认为这些答案是全部。对于我的模拟,我懒惰地生成随机数,并在很小的空间(我的 macbook 上为 1.1M)中严格运行它们。
也许随机数只能存在于 IO monad 中的评论指的是真正的随机数,但对于伪随机数来说并非如此,通常人们希望能够重现结果。这是一个例子:
module Main (
main
) where
import qualified Data.Vector.Unboxed as V
import Data.Random.Source.PureMT
import Data.Random
import Control.Monad.State
nItt :: Int
nItt = 1000000000
gridSize :: Int
gridSize = 10
testData :: Int -> V.Vector Double
testData m =
V.fromList $
evalState (replicateM m (sample (uniform (0 :: Double) 1.0)))
(pureMT 2)
test = V.foldl (+) 0 (testData nItt)
main = putStrLn $ show test
如果您想要真正的随机性,您将无法使用 IO - 听起来很麻烦,但这种分离是 Haskell 的一个非常重要的方面。但是,您可以通过自己选择“种子”并使用 System.Random 中返回一对结果和新种子(例如“随机”)的纯函数来获得半伪随机性。