Haskell中的随机整数

时间:2014-03-20 14:15:00

标签: haskell random

我正在学习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

因此,当我尝试将其包装起来时,它失败了,我不明白为什么。

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。

2 个答案:

答案 0 :(得分:2)

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

相反,您有几个选择。您可以将依赖于随机生成的值的所有代码编写为纯函数,并仅执行IO以在main或类似函数中获取标准生成器,或者您可以使用提供的MonadRandom包你有pre-built monad来管理随机值。由于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!"

这里我们必须手动管理生成器的状态,我们没有得到方便的操作,使我们的执行顺序更加清晰(懒惰有助于第二种情况,但它使它更混乱) 。手动管理这种状态是无聊,乏味和容易出错的。 Rand monad使一切变得更容易,更清晰,并减少了错误的机会。这通常是在Haskell中进行随机数生成的首选方法。


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

答案 1 :(得分:0)

我认为上述情况有点误导。如果你使用状态monad,那么你可以这样做:

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

请参阅此处以获取扩展的使用示例:Markov Chain Monte Carlo