我编写了一个函数以生成两个随机数,然后将其传递给另一个函数以在其中使用它们。的代码是:
randomIntInRange :: (Int, Int, Int, Int) -> Board
randomIntInRange (min,max,min2,max2) = do r <- randomRIO (min, max)
r2 <- randomRIO (min2, max2)
randomCherryPosition (r, r2)
此函数在其“ do”块中调用的函数是:
randomCherryPosition :: (Int, Int) -> Board
randomCherryPosition (x, y) = initialBoard & element y . element x .~ C
其中initialBoard
是列表列表,C是预定义的数据类型。我正在使用lens
更改列表中的值。运行此命令会给我错误:
Couldn't match type ‘IO’ with ‘[]’
Expected type: [Int]
Actual type: IO Int
对于r和r2行。我绝对不知道这是怎么回事,或者我做错了什么,所以我将不胜感激。
答案 0 :(得分:3)
randomRIO
的类型为IO Int
,而不是Int
。只要您使用任何IO
函数,您周围的函数也必须位于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
不是纯函数。每次返回一个不同的值。 Haskell禁止此类功能。禁止使用此类功能有很多好处,我将在这里介绍。但是,如果将其包装在IO
中,则可以仍然具有这种功能。类型IO Int
的意思是“ 它是一个程序,在执行时会产生一个Int
”。因此,当您调用randomRIO (min, max)
时,它不会返回一个Int
,而是一个程序,您可以执行该程序以获取一个Int
。您可以通过带有左箭头的do
表示法执行程序,但是结果也将是类似的程序。
答案 1 :(得分:3)
不幸的是,没有完美的解决方案来解决这个问题。已经在Stackoverflow上进行了讨论,例如here。
Fyodor提供的上述解决方案涉及IO。有用。主要缺点是IO会传播到您的类型系统中。
但是,并非仅仅因为您要使用随机数就必须介入IO。 there深入讨论了所涉及的利弊。
没有完美的解决方案,因为每次您选择一个随机值时,某物都必须负责更新随机数生成器的 state 。在命令式语言(例如C / C ++ / Fortran)中,我们为此使用副作用。但是Haskell函数没有副作用。这样某物可以是:
randomRIO
)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更不易出错,因此,一旦计算变得足够复杂,您通常会首选此解决方案。所有涉及的功能都是普通的 pure Haskell函数,编译器可以使用其常用技术对其进行完全优化。