残念ながら、この問題に対する完全な解決策はありません。すでに Stackoverflow で議論されています。
Fyodor が提供する上記のソリューションには IO が含まれます。できます。主な欠点は、IO が型システムに伝播することです。
ただし、乱数を使用したいという理由だけで IO を含める必要はありません。関連する長所と短所の詳細な議論は、そこにあります。
乱数を選択するたびに乱数ジェネレーターの状態を更新する必要があるため、完全な解決策はありません。C/C++/Fortran などの命令型言語では、これに副作用を使用します。しかし、Haskell 関数には副作用がありません。何かができるように:
- Haskell IO サブシステム (のように
randomRIO
)
- あなた自身がプログラマーです - 以下のコードサンプル #1 を参照してください
- より特殊化された 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 関数であり、コンパイラは通常の手法を使用して完全に最適化できます。