2012-06-15 255 views
6

有人可以描述以下類型的構造函數和函數是如何工作的嗎?Haskell隨機生成

type Rand a = State StdGen a 

getRandom :: (Random a) => Rand a 
getRandom = get >>= (\r -> let (a,g) = random r in (put g) >> (return a)) 

runRand :: Int -> Rand a -> a 
runRand n r = evalState r $ mkStdGen n 

runRandIO :: Rand a -> IO a 
runRandIO r = randomIO >>= (\rnd -> return $ runRand rnd r) 

getRandoms :: (Random a) => Int -> Rand [a] 
getRandoms n = mapM (\_ -> getRandom) [1..n] 

回答

8

讓我們從頭開始:

type Rand a = State StdGen a 

這行告訴你,Rand aState類型,其狀態由StdGen給出,其最終的價值a類型的類型代名詞。這將用於存儲每個隨機數請求之間的隨機數生成器的狀態。

getRandom的代碼可以被轉換成做記號:

getRandom :: (Random a) => Rand a 
getRandom = do 
    r <- get     -- get the current state of the generator 
    let (a,g) = random r in do -- call the function random :: StdGen -> (a, StdGen) 
    put g     -- store the new state of the generator 
    return a     -- return the random number that was generated 

runRand函數接受一個初始種子nRand a類型的值r(其,請記住,僅僅是State StdGen a的同義詞)。它創建一個mkStdGen n的新生成器並將其提供給evalState r。函數evalState僅評估State s a類型的返回值,忽略狀態。

同樣,我們可以轉換成runRandIO符號do

runRandIO :: Rand a -> IO a 
runRandIO r = do 
    rnd <- randomIO  -- generate a new random number using randomIO 
    return (runRand rnd r) -- use that number as the initial seed for runRand 

最後,getRandoms花費數n表示要生成的隨機值的數量。它建立一個列表[1..n]並將getRandom應用到列表中。請注意,[1..n]中的實際值未使用(可以看出,因爲lambda函數以\_ -> ...開頭)。該列表只是爲了擁有正確數量的元素。由於getRandom返回一元值,因此我們使用mapM來映射列表,這會導致狀態(即StdGen)正確地通過對getRandom的每個調用進行線程化。

5

基本思想很簡單 - 創建僞隨機數,你需要在函數調用之間保持一些狀態。所以類型Rand a被定義爲「a以及隨機性所需的狀態」。

狀態使用State monad存儲。這提供了兩個主要行動 - getput,它們的確聽起來很像。因此getRandom只是查詢當前狀態,然後調用random函數。該函數返回兩個值:隨機值和新狀態。然後你只需put新的狀態幷包裝結果值。

runRand允許您打開給定種子的「隨機」值。 evalState可讓您執行有狀態計算(即,類型爲State s a或在此情況下爲Rand a)的值,並給出初始狀態,然後放棄最終狀態,只給出結果。因此,您可以使用給定種子運行Rand a,並僅返回結果值。值可以是a而不是Rand a,因爲它總是會給你相同的結果。

runRandomIO只是做相同的事情,除了基於IO的種子基於一些全局狀態。

getRandoms只是通過調用getRandom[1..n]列表(忽略實際數)的每一個元素讓你Rand a值的列表。