Random Integer in Haskell [duplicate]

2020-05-06 00:40发布

I am in the process of learning Haskell and to learn I want to generate a random Int type. I am confused because the following code works. Basically, I want an Int not an IO Int.

In ghci this works:

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

So when I try to wrap this up with do it fails and I don't understand why.

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

The compiler produces the following:

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.

I am new to Haskell, so if there is a better way to generate a random Int without do that would be preferred.

So my question is, why does my function not work and is there a better way to get a random Int.

2条回答
别忘想泡老子
2楼-- · 2020-05-06 01:20

The simple answer is that you can't generate random numbers without invoking some amount of IO. Whenever you get the standard generator, you have to interact with the host operating system and it makes a new seed. Because of this, there is non-determinism with any function that generates random numbers (the function returns different values for the same inputs). It would be like wanting to be able to get input from STDIN from the user without it being in the IO monad.

Instead, you have a few options. You can write all your code that depends on a randomly generated value as pure functions and only perform the IO to get the standard generator in main or some similar function, or you can use the MonadRandom package that gives you a pre-built monad for managing random values. Since most every pure function in System.Random takes a generator and returns a tuple containing the random value and a new generator, the Rand monad abstracts this pattern out so that you don't have to worry about it. You can end up writing code like

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!"

There's also an accompanying monad transformer, but I'd hold off on that until you have a good grasp on monads themselves. Compare this code to using 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!"

Here we have to manually have to manage the state of the generator and we don't get the handy do-notation that makes our order of execution more clear (laziness helps in the second case, but it makes it more confusing). Manually managing this state is boring, tedious, and error prone. The Rand monad makes everything much easier, more clear, and reduces the chance for bugs. This is usually the preferred way to do random number generation in Haskell.


It is worth mentioning that you can actually "unwrap" an IO a value to just an a value, but you should not use this unless you are 100% sure you know what you're doing. There is a function called unsafePerformIO, and as the name suggests it is not safe to use. It exists in Haskell mainly for when you are interfacing with the FFI, such as with a C DLL. Any foreign function is presumed to perform IO by default, but if you know with absolute certainty that the function you're calling has no side effects, then it's safe to use unsafePerformIO. Any other time is just a bad idea, it can lead to some really strange behaviors in your code that are virtually impossible to track down.

查看更多
Summer. ? 凉城
3楼-- · 2020-05-06 01:34

I think the above is slightly misleading. If you use the state monad then you can do something like this:

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

See here for an extended example of usage:Markov Chain Monte Carlo

查看更多
登录 后发表回答