不幸的是,这个问题没有完美的解决方案。它已经在 Stackoverflow 上进行了讨论,例如这里。
Fyodor提供的上述解决方案涉及IO。有用。主要缺点是 IO 会传播到您的类型系统中。
然而,仅仅因为你想使用随机数就涉及到 IO 并不是绝对必要的。那里可以深入讨论所涉及的利弊。
没有完美的解决方案,因为每次您选择随机值时,都必须处理更新随机数生成器的状态。在 C/C++/Fortran 等命令式语言中,我们为此使用副作用。但是 Haskell 函数没有副作用。所以可以是:
- Haskell IO 子系统(如
randomRIO
)
- 自己作为程序员 - 请参阅下面的代码示例 #1
- 一个更专业的 Haskell 子系统,您需要:
import Control.Monad.Random
- 请参阅下面的代码示例 #2
您可以通过创建自己的随机数生成器,使用库函数mkStdGen
,然后在计算过程中手动传递此生成器的更新状态,从而在不涉及 IO 的情况下解决问题。在您的问题中,这给出了以下内容:
-- 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 更不容易出错,所以一旦你的计算变得足够复杂,你通常会更喜欢这个解决方案。所涉及的所有函数都是普通的纯Haskell 函数,编译器可以使用其常用技术对其进行全面优化。