0

2 つの乱数を生成する関数を作成し、それを別の関数に渡してそこで使用します。このコードは次のとおりです。

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 行の両方で。ここで何が起こっているのか、何が間違っているのかまったくわからないので、助けていただければ幸いです。

4

2 に答える 2

3

残念ながら、この問題に対する完全な解決策はありません。すでに Stackoverflow で議論されています

Fyodor が提供する上記のソリューションには IO が含まれます。できます。主な欠点は、IO が型システムに伝播することです。

ただし、乱数を使用したいという理由だけで IO を含める必要はありません。関連する長所と短所の詳細な議論は、そこにあります。

乱数を選択するたびに乱数ジェネレーターの状態を更新する必要があるため、完全な解決策はありませんC/C++/Fortran などの命令型言語では、これに副作用を使用します。しかし、Haskell 関数には副作用がありません。何かできるように:

  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 関数であり、コンパイラは通常の手法を使用して完全に最適化できます。

于 2019-10-23T23:26:08.930 に答える