无法将类型“IO”与“[]”匹配

Couldn't match type ‘IO’ with ‘[]’

我编写了一个函数来生成两个随机数,然后将其传递给另一个函数以在那里使用它们。代码是:

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 行。我完全不知道这里发生了什么,或者我做错了什么,所以我将不胜感激任何帮助。

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 不是纯函数。它 returns 每次都是不同的值。 Haskell 禁止此类功能。禁止此类功能有很多好处,我将在此处进行介绍。但是你可以仍然有这样的功能,如果你把它包装在IO中。类型 IO Int 表示“它是一个程序,在执行时将产生一个 Int”。因此,当您调用 randomRIO (min, max) 时,它 returns 您不是一个 Int,而是一个程序,您可以执行该程序以获得 Int。您通过带有向左箭头的 do 符号来执行,但其结果也将是一个类似的程序。

不幸的是,这个问题没有完美的解决方案。它已经在 Whosebug 上讨论过,例如

Fyodor提供的上述解决方案涉及到IO。有用。主要缺点是 IO 会传播到您的类型系统中。

但是,并不一定要因为要使用随机数就涉及到IO。可以深入讨论所涉及的利弊 there

没有完美的解决方案,因为每次选择时 something 必须负责更新随机数生成器的 state一个随机值。在 C/C++/Fortran 等命令式语言中,我们为此使用 side effects。但是 Haskell 函数没有副作用。这样 something 可以是:

  1. Haskell IO 子系统(如 randomRIO
  2. 你自己作为程序员 - 请参阅下面的代码示例 #1
  3. 一个更专业的 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 更不容易出错,因此一旦您的计算变得足够复杂,您通常会更喜欢此解决方案。所有涉及的函数都是普通的 pure Haskell 函数,编译器可以使用其通常的技术对其进行充分优化。