Unfortunately there is no perfect solution to this problem. It has already been discussed on Stackoverflow, for example here.
The above solution provided by Fyodor involves IO. It works. The main drawback is that IO will propagate into your type system.
However, it is not strictly necessary to involve IO just because you want to use random numbers. An in-depth discussion of the pros and cons involved is available there.
There is no perfect solution, because something has to take care of updating the state of the random number generator every time you pick a random value. In imperative languages such as C/C++/Fortran, we use side effects for this. But Haskell functions have no side effects. So that something can be:
- the Haskell IO subsystem (as in
randomRIO
)
- yourself as the programmer - see code sample #1 below
- a more specialized Haskell subsystem, for which you need to have:
import Control.Monad.Random
- see code sample #2 below
You can solve the problem without involving IO by creating your own random number generator, using library function mkStdGen
, and then passing the updated states of this generator manually around your computations. In your problem, this gives something like this:
-- code sample #1
import System.Random
-- just for type check:
data Board = Board [(Int, Int)] deriving Show
initialBoard :: Board
initialBoard = Board [(0, 0)]
randomCherryPosition :: (Int, Int) -> Board
randomCherryPosition (x, y) = -- just for type check
let ls0 = (\(Board ls) -> ls) initialBoard
ls1 = (x, y) : ls0
in Board ls1
-- initial version with IO:
randomIntInRange :: (Int, Int, Int, Int) -> IO Board
randomIntInRange (min,max, min2,max2) = do r1 <- randomRIO (min, max)
r2 <- randomRIO (min2, max2)
return $ randomCherryPosition (r1, r2)
-- version with manual passing of state:
randomIntInRangeA :: RandomGen tg => (Int, Int, Int, Int) -> tg -> (Board, tg)
randomIntInRangeA (min1,max1, min2,max2) rng0 =
let (r1, rng1) = randomR (min1, max1) rng0
(r2, rng2) = randomR (min2, max2) rng1 -- pass the newer RNG
board = randomCherryPosition (r1, r2)
in (board, rng2)
main = do
-- get a random number generator:
let mySeed = 54321 -- actually better to pass seed from the command line.
let rng0 = mkStdGen mySeed
let (board1, rng) = randomIntInRangeA (0,10, 0,100) rng0
putStrLn $ show board1
This is cumbersome but can be made to work.
A more elegant alternative consists in using MonadRandom.
The idea is to define a monadic action representing the randomness-involving computation, and then to run this action using the aptly named runRand
function.
This gives this code instead:
-- code sample #2
import System.Random
import Control.Monad.Random
-- just for type check:
data Board = Board [(Int, Int)] deriving Show
initialBoard :: Board
initialBoard = Board [(0, 0)]
-- just for type check:
randomCherryPosition :: (Int, Int) -> Board
randomCherryPosition (x, y) =
let ls0 = (\(Board ls) -> ls) initialBoard
ls1 = (x, y) : ls0
in Board ls1
-- monadic version of randomIntInRange:
randomIntInRangeB :: RandomGen tg => (Int, Int, Int, Int) -> Rand tg Board
randomIntInRangeB (min1,max1, min2,max2) =
do
r1 <- getRandomR (min1,max1)
r2 <- getRandomR (min2,max2)
return $ randomCherryPosition (r1, r2)
main = do
-- get a random number generator:
let mySeed = 54321 -- actually better to pass seed from the command line.
let rng0 = mkStdGen mySeed
-- create and run the monadic action:
let action = randomIntInRangeB (0,10, 0,100) -- of type: Rand tg Board
let (board1, rng) = runRand action rng0
putStrLn $ show board1
This is definitely less error prone than code sample #1, so you would typically prefer this solution as soon as your computations become complex enough. All the functions involved are ordinary pure Haskell functions, which the compiler can fully optimize using its usual techniques.