【问题标题】:Couldn't match type ‘IO’ with ‘[]’无法将类型“IO”与“[]”匹配
【发布时间】:2020-02-19 22:12:23
【问题描述】:

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

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

【问题讨论】:

    标签: haskell random io haskell-lens gloss


    【解决方案1】:

    不幸的是,这个问题没有完美的解决方案。它已经在 Stackoverflow 上进行了讨论,例如 here

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

    但是,仅仅因为您想使用随机数就涉及 IO 并不是绝对必要的。可以通过there 深入讨论所涉及的利弊。

    没有完美的解决方案,因为每次您选择随机值时,something 都必须负责更新随机数生成器的状态。在 C/C++/Fortran 等命令式语言中,我们为此使用副作用。但是 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 更不容易出错,因此一旦您的计算变得足够复杂,您通常会更喜欢这个解决方案。所涉及的所有函数都是普通的 Haskell 函数,编译器可以使用其常用技术对其进行全面优化。

    【讨论】:

      【解决方案2】:

      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 符号执行执行,但结果也将是一个类似的程序。

      【讨论】:

      • 是的,我也是这么想的,但是这样做并不能解决原来的问题,而是创建了一个新问题,它告诉我“randomIntInRange”返回“Board”而不是预期的“IO Board”类型'
      猜你喜欢
      • 2021-03-24
      • 2014-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多