【问题标题】:Random Integer in Haskell [duplicate]Haskell中的随机整数[重复]
【发布时间】:2014-03-21 00:40:49
【问题描述】:

我正在学习 Haskell 并学习我想生成一个随机 Int 类型。我很困惑,因为以下代码有效。基本上,我想要一个 Int 而不是 IO Int。

在 ghci 中这有效:

Prelude> import System.Random
Prelude System.Random> foo <- getStdRandom (randomR (1,1000000))
Prelude System.Random> fromIntegral foo :: Int
734077
Prelude System.Random> let bar = fromIntegral foo :: Int
Prelude System.Random> bar
734077
Prelude System.Random> :t bar
bar :: Int

所以当我尝试用 do 来结束它时,它失败了,我不明白为什么。

randomInt = do
    tmp <- getStdRandom (randomR (1,1000000))
    fromIntegral tmp :: Int

编译器生成以下内容:

Couldn't match expected type `IO b0' with actual type `Int'
In a stmt of a 'do' block: fromIntegral tmp :: Int
In the expression:
  do { tmp <- getStdRandom (randomR (1, 1000000));
         fromIntegral tmp :: Int }
In an equation for `randomInt':
    randomInt
      = do { tmp <- getStdRandom (randomR (1, 1000000));
               fromIntegral tmp :: Int }
Failed, modules loaded: none.

我是 Haskell 的新手,所以如果有更好的方法来生成随机 Int 而不这样做,那将是首选。

所以我的问题是,为什么我的函数不起作用,有没有更好的方法来获取随机 Int。

【问题讨论】:

标签: haskell random


【解决方案1】:

简单的答案是,如果不调用一定数量的 IO,就无法生成随机数。每当您获得标准生成器时,您都必须与主机操作系统进行交互,并且它会生成一个新种子。因此,任何生成随机数的函数都存在不确定性(该函数为相同的输入返回不同的值)。这就像希望能够从用户那里获得来自 STDIN 的输入,而不是在 IO monad 中。

相反,您有几个选择。您可以将依赖于随机生成的值的所有代码编写为纯函数,并且只执行 IO 以在 main 或类似函数中获取标准生成器,或者您可以使用为您提供 @987654322 的 MonadRandom 包@ 用于管理随机值。由于System.Random 中的大多数纯函数都接受一个生成器并返回一个包含随机值和一个新生成器的元组,Rand monad 将这个模式抽象出来,这样你就不必担心它了。您最终可以编写类似的代码

import Control.Monad.Random hiding (Random)
type Random a = Rand StdGen a

rollDie :: Int -> Random Int
rollDie n = getRandomR (1, n)

d6 :: Random Int
d6 = rollDie 6

d20 :: Random Int
d20 = rollDie 20

magicMissile :: Random (Maybe Int)
magicMissile = do
    roll <- d20
    if roll > 15
        then do
            damage1 <- d6
            damage2 <- d6
            return $ Just (damage1 + damage2)
        else return Nothing

main :: IO ()
main = do
    putStrLn "I'm going to cast Magic Missile!"
    result <- evalRandIO magicMissile
    case result of
        Nothing -> putStrLn "Spell fizzled"
        Just d  -> putStrLn $ "You did " ++ show d ++ " damage!"

还有一个附带的 monad 转换器,但我会推迟它,直到你很好地掌握了 monad 本身。将此代码与使用 System.Random 进行比较:

rollDie :: Int -> StdGen -> (Int, StdGen)
rollDie n g = randomR (1, n) g

d6 :: StdGen -> (Int, StdGen)
d6 = rollDie 6

d20 :: StdGen -> (Int, StdGen)
d20 = rollDie 20

magicMissile :: StdGen -> (Maybe Int, StdGen)
magicMissile g =
    let (roll, g1) = d20 g
        (damage1, g2) = d6 g1
        (damage2, g3) = d6 g2
    in if roll > 15
        then (Just $ damage1 + damage2, g3)
        else Nothing

main :: IO ()
main = do
    putStrLn "I'm going to case Magic Missile!"
    g <- getStdGen
    let (result, g1) = magicMissile g
    case result of
        Nothing -> putStrLn "Spell fizzled"
        Just d  -> putStrLn $ "You did " ++ show d ++ " damage!"

这里我们必须手动管理生成器的状态,并且我们没有得到方便的 do-notation 使我们的执行顺序更清晰(懒惰在第二种情况下有所帮助,但它会使它更加混乱) .手动管理此状态很无聊、乏味且容易出错。 Rand monad 让一切变得更容易、更清晰,并减少了出现错误的机会。这通常是在 Haskell 中生成随机数的首选方式。


值得一提的是,您实际上可以将一个 IO a 值“解包”为一个 a 值,但除非您 100% 确定自己知道自己在做什么,否则不应使用它。有一个名为unsafePerformIO 的函数,顾名思义,它使用起来并不安全。它存在于 Haskell 中,主要用于与 FFI 交互时,例如与 C DLL 交互。默认情况下,假定任何外部函数都执行IO,但如果您绝对确定您正在调用的函数没有副作用,那么使用unsafePerformIO 是安全的。任何其他时间都是一个坏主意,它可能会导致代码中出现一些非常奇怪的行为,这些行为几乎无法追踪。

【讨论】:

  • 我认为这有点误导。如果你想使用伪随机数,那么你可以使用 state monad,例如> acceptOrRejects :: Int -> Int -> [Double] > acceptOrRejects seed nIters = > evalState (replicateM nIters (sample stdUniform)) > (pureMT $ fromIntegral seed)
  • 嗯,格式在 cmets 中似乎不太好用。我会尝试一个答案。
  • @DominicSteinitz 你能详细说明它到底有什么误导性吗?此外,Rand monad 实际上只是 State monad 的包装,那么为什么不使用它呢?
  • “如果不调用一定数量的 IO,就无法生成随机数”会让人认为一个人必须使用 IO。也许这不是你的意思?我认为 Haskell 中有一个非常好的模型,可以懒惰地生成随机数并严格使用它们,正如我希望我的答案中的示例所示。
  • @DominicSteinitz 在某些时候,您必须为随机生成器提供种子。在我的例子中,它发生在evalRandIO,通常你从getStdGen 得到它。如果您不提供生成器,那么您的程序就不是随机的(甚至是伪随机的)。种子必须来自某个地方,如果它被硬编码到你的程序中,那么它就不是真正的种子,如果它没有被硬编码,那么它必须来自调用 IO 的外部源。您可以纯粹从种子中生成随机数,这正是 Rand 所做的,但无论如何启动它都需要 IO。
【解决方案2】:

我认为以上内容有点误导。如果你使用 state monad,那么你可以这样做:

acceptOrRejects :: Int -> Int -> [Double]
acceptOrRejects seed nIters =
  evalState (replicateM nIters (sample stdUniform))
  (pureMT $ fromIntegral seed)

更多使用示例请参见此处:Markov Chain Monte Carlo

【讨论】:

    猜你喜欢
    • 2014-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多