I wrote a function to generate two random numbers, which I then pass to a different function to use them there. The code for this is:
randomIntInRange :: (Int, Int, Int, Int) -> Board
randomIntInRange (min,max,min2,max2) = do r <- randomRIO (min, max)
r2 <- randomRIO (min2, max2)
randomCherryPosition (r, r2)
And the function this function calls in its 'do' block is:
randomCherryPosition :: (Int, Int) -> Board
randomCherryPosition (x, y) = initialBoard & element y . element x .~ C
Where initialBoard
is a list of lists and C is a predefined data type. I am using lens
to change values inside the list. Running this gives me the error:
Couldn't match type ‘IO’ with ‘[]’
Expected type: [Int]
Actual type: IO Int
for both r and r2 lines. I have absolutely no idea what is going on here, or what i'm doing wrong, so I would greatly appreciate any help.
randomRIO
has type IO Int
, not Int
. As long as you use any IO
functions, your surrounding function must also be in IO
:
randomIntInRange :: (Int, Int, Int, Int) -> IO Board
randomIntInRange (min,max,min2,max2) = do r <- randomRIO (min, max)
r2 <- randomRIO (min2, max2)
pure $ randomCherryPosition (r, r2)
randomRIO
is not a pure function. It returns a different value every time. Haskell bans such functions. There are numerous benefits that come from banning such functions, which I'm going to go into here. But you can still have such function if you wrap it in IO
. The type IO Int
means " it's a program that, when executed, will produce an Int
". So when you call randomRIO (min, max)
, it returns you not an Int
, but a program, which you can then execute to get an Int
. You do the execution via the do
notation with left arrow, but the result of that would also be a similar program.
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:
randomRIO
) 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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.