0

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.

csnate
  • 1,601
  • 4
  • 19
  • 31
  • I would suggest you to go through this chapter: http://learnyouahaskell.com/input-and-output – Sibi Mar 20 '14 at 14:17
  • See: http://stackoverflow.com/questions/11047373/haskell-random-generation and http://stackoverflow.com/questions/19594655/random-number-in-haskell – Mau Mar 20 '14 at 14:26
  • use randomRIO instead of randomR – karakfa Mar 20 '14 at 14:33
  • If you do not want an `IO Int`, then you're not able to use `getStdRandom`. After all, you want to use the global `StdGen`, generate a value from it, and change the global `StdGen`. This action has side-effects and therefor resides in the `IO` monad. By the way, the answer depends a little bit on the context where you use `randomInt`, so you should add that to your question. – Zeta Mar 20 '14 at 14:44
  • Can I 'extract' (for lack of a better term) the underlying Int from the monad IO? Or better yet, can I use an IO Int just as I would a function that takes an Int? – csnate Mar 20 '14 at 14:56
  • No, you can't; that is how IO side-effects are kept apart from effect-less functions in Haskell. Things you *can* do include using `return` to make an `IO Int` from an `Int`, using `<-` inside a `do` block to give a name to the underlying `Int` and then use it directly, and using `fmap` to apply a function with an `Int` argument to an `IO Int`. – duplode Mar 20 '14 at 15:04
  • @csnate There are lots of functions that stitch together an `IO Int` value with a function that takes an `Int`. In fact, that's the whole pattern underlying Haskell's IO system. The fact that all of them leave the result in `IO` is also a feature, but don't mistake that for meaning you need `IO` everywhere. If you can write most of your functionality without any `IO` in the type signatures, you benefit from it greatly. – Carl Mar 20 '14 at 15:25

2 Answers2

3

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.

Zeta
  • 103,620
  • 13
  • 194
  • 236
bheklilr
  • 53,530
  • 6
  • 107
  • 163
  • I think this is a bit misleading. If you want to use pseudo-random numbers then you can use the state monad e.g. > acceptOrRejects :: Int -> Int -> [Double] > acceptOrRejects seed nIters = > evalState (replicateM nIters (sample stdUniform)) > (pureMT $ fromIntegral seed) – idontgetoutmuch Mar 20 '14 at 16:35
  • Hmmm the formatting doesn't seem work very well in comments. I will try an answer. – idontgetoutmuch Mar 20 '14 at 16:36
  • @DominicSteinitz Can you elaborate on what exactly about it is misleading? Also, the `Rand` monad is literally just a wrapper around the `State` monad, so why not use it? – bheklilr Mar 20 '14 at 17:04
  • "you can't generate random numbers without invoking some amount of IO" would lead one to think that one *had* to use IO. Perhaps this is not what you meant? I think there is quite a nice model in Haskell of generating random numbers lazily and consuming them strictly as I hope the example in my answer shows. – idontgetoutmuch Mar 20 '14 at 17:47
  • 1
    @DominicSteinitz At some point you have to provide a seed to your random generator. In my example, it happens in `evalRandIO`, normally you get it from `getStdGen`. If you don't provide a generator, then your program isn't random (or even pseudo-random). The seed has to come from somewhere, if it is hardcoded into your program then it isn't a true seed, and if it isn't hardcoded then it must come from an external source, which invokes IO. You can generate random numbers off of a seed purely, which is what `Rand` does, but kicking it off requires IO no matter what. – bheklilr Mar 20 '14 at 17:58
0

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

idontgetoutmuch
  • 1,621
  • 11
  • 17