简体   繁体   中英

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

I wrote a function to generate two random numbers, which I then pass to a different function to use them there. The code for this is:

randomIntInRange :: (Int, Int, Int, Int) -> Board
randomIntInRange (min,max,min2,max2) = do r <- randomRIO (min, max)
                                          r2 <- randomRIO (min2, max2)
                                          randomCherryPosition (r, r2)

And the function this function calls in its 'do' block is:

randomCherryPosition :: (Int, Int) -> Board
randomCherryPosition (x, y) = initialBoard & element y . element x .~ C

Where initialBoard is a list of lists and C is a predefined data type. I am using lens to change values inside the list. Running this gives me the error:

Couldn't match type ‘IO’ with ‘[]’
      Expected type: [Int]
        Actual type: IO Int

for both r and r2 lines. I have absolutely no idea what is going on here, or what i'm doing wrong, so I would greatly appreciate any help.

randomRIO has type IO Int , not Int . As long as you use any IO functions, your surrounding function must also be in 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 is not a pure function. It returns a different value every time. Haskell bans such functions. There are numerous benefits that come from banning such functions, which I'm going to go into here. But you can still have such function if you wrap it in IO . The type IO Int means " it's a program that, when executed, will produce an Int ". So when you call randomRIO (min, max) , it returns you not an Int , but a program, which you can then execute to get an Int . You do the execution via the do notation with left arrow, but the result of that would also be a similar program.

Unfortunately there is no perfect solution to this problem. It has already been discussed on Stackoverflow, for example here .

The above solution provided by Fyodor involves IO. It works. The main drawback is that IO will propagate into your type system.

However, it is not strictly necessary to involve IO just because you want to use random numbers. An in-depth discussion of the pros and cons involved is available there .

There is no perfect solution, because something has to take care of updating the state of the random number generator every time you pick a random value. In imperative languages such as C/C++/Fortran, we use side effects for this. But Haskell functions have no side effects. So that something can be:

  1. the Haskell IO subsystem (as in randomRIO )
  2. yourself as the programmer - see code sample #1 below
  3. a more specialized Haskell subsystem, for which you need to have: import Control.Monad.Random - see code sample #2 below

You can solve the problem without involving IO by creating your own random number generator, using library function mkStdGen , and then passing the updated states of this generator manually around your computations. In your problem, this gives something like this:

-- 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

This is cumbersome but can be made to work.

A more elegant alternative consists in using MonadRandom . The idea is to define a monadic action representing the randomness-involving computation, and then to run this action using the aptly named runRand function. This gives this code instead:

-- 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

This is definitely less error prone than code sample #1, so you would typically prefer this solution as soon as your computations become complex enough. All the functions involved are ordinary pure Haskell functions, which the compiler can fully optimize using its usual techniques.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM