Не удалось сопоставить тип 'IO' с '[]' - PullRequest
0 голосов
/ 23 октября 2019

Я написал функцию для генерации двух случайных чисел, которые я затем передал другой функции, чтобы использовать их там. Код для этого:

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. Я абсолютно не знаю, что здесь происходит, или что я делаю неправильно, поэтому я был бы очень признателен за любую помощь.

Ответы [ 2 ]

3 голосов
/ 24 октября 2019

К сожалению, нет идеального решения этой проблемы. Это уже обсуждалось на Stackoverflow, например здесь .

Вышеупомянутое решение, предоставленное Федором, включает IO. Оно работает. Основным недостатком является то, что IO будет распространяться в вашей системе типов.

Однако нет необходимости использовать IO только потому, что вы хотите использовать случайные числа. Подробное обсуждение всех «за» и «против» доступно там .

Идеального решения не существует, потому что что-то должно позаботиться об обновлении состояние генератора случайных чисел каждый раз, когда вы выбираете случайное значение. В императивных языках, таких как C / C ++ / Fortran, мы используем для этого побочные эффекты . Но функции Haskell не имеют побочных эффектов. Так что что-то может быть:

  1. подсистемой ввода-вывода Haskell (как в randomRIO)
  2. как программист - см. Пример кода № 1 ниже
  3. более специализированная подсистема Haskell, для которой вам необходимо иметь: import Control.Monad.Random - см. Пример кода № 2 ниже

Вы можете решить проблему без привлечения ввода-вывода, создав собственный случайный кодгенератор чисел, используя библиотечную функцию mkStdGen, а затем передавая обновленные состояния этого генератора вручную вокруг ваших вычислений. В вашей задаче это выглядит примерно так:

-- 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, которые компилятор может полностью оптимизировать, используя свои обычные методы.

3 голосов
/ 23 октября 2019

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 со стрелкой влево, но результатом будет также аналогичная программа.

...