К сожалению, нет идеального решения этой проблемы. Это уже обсуждалось на Stackoverflow, например здесь .
Вышеупомянутое решение, предоставленное Федором, включает IO. Оно работает. Основным недостатком является то, что IO будет распространяться в вашей системе типов.
Однако нет необходимости использовать IO только потому, что вы хотите использовать случайные числа. Подробное обсуждение всех «за» и «против» доступно там .
Идеального решения не существует, потому что что-то должно позаботиться об обновлении состояние генератора случайных чисел каждый раз, когда вы выбираете случайное значение. В императивных языках, таких как C / C ++ / Fortran, мы используем для этого побочные эффекты . Но функции Haskell не имеют побочных эффектов. Так что что-то может быть:
- подсистемой ввода-вывода Haskell (как в
randomRIO
) - как программист - см. Пример кода № 1 ниже
- более специализированная подсистема Haskell, для которой вам необходимо иметь:
import Control.Monad.Random
- см. Пример кода № 2 ниже
Вы можете решить проблему без привлечения ввода-вывода, создав собственный случайный кодгенератор чисел, используя библиотечную функцию mkStdGen
, а затем передавая обновленные состояния этого генератора вручную вокруг ваших вычислений. В вашей задаче это выглядит примерно так:
-- 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
Это громоздко, но можно заставить работать.
Более элегантная альтернатива состоит в использовании MonadRandom . Идея состоит в том, чтобы определить монадическое действие, представляющее вычисление с участием случайности, а затем запустить это действие с использованием метко названной функции runRand
. Вместо этого это дает следующий код:
-- 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
Это определенно менее подвержено ошибкам, чем образец кода # 1, поэтому вы, как правило, предпочтете это решение, как только ваши вычисления станут достаточно сложными. Все задействованные функции - это обычные функции pure Haskell, которые компилятор может полностью оптимизировать, используя свои обычные методы.