2012-02-15 40 views
0

我正在嘗試使用MonadRandom。我把它放到了randomPref的功能中,但是整件事情都事後起來了!任何提示都表示讚賞。如何重構代碼以使用MonadRandom

module AgentGenerator where 

import System.Random 
import Data.Hashable 
import Control.Monad.Random 

import System.Environment 

-- Generate agents and write to a file 
-- 'fname' - output filename 
-- 's' - number of agent sets 
-- 'n' - number of agents in a set 
-- 'x' - number of preferences per agent 
generate fname s n x = do 
    writeFile fname $ show $ generateAgentSets s n x 

-- Agent type: Name, List of preferences 
data Agent = Agent String [Double] deriving Show 
type AgentSet = [Agent] 

-- Generate 's' sets of 'n' agents each, with 'x' preferences each 
generateAgentSets:: Integer -> Integer -> Integer -> [AgentSet] 
generateAgentSets s n x = [generateAgents n x | i <- [1..s] ] 

-- Generate n agents with 'x' preferences each 
generateAgents:: Integer -> Integer -> AgentSet 
generateAgents n x = [createAgent (show i) x | i <- [1..n]] 

-- Create agent 'name' with 'x' preferences 
createAgent:: String -> Integer -> Agent 
createAgent name x = Agent name prefs where 
    prefs = [ randomPref (i + hashed) | i <- [1..x] ] where 
     hashed = fromIntegral (hash name) 

-- Generate single random value between [0, 1] based on the seed 
-- TODO: Get rid of the seed thing and use MonadRandom instead 
randomPref :: (RandomGen g) => Integer -> Rand g [Double] 
randomPref seed = getRandomR (0.0, 1.0) 

回答

10

您已經定義Agent

data Agent = Agent String [Double] 

但在createAgent,您嘗試使用類型

Agent String [Rand g [Double]] 

另一種類型的錯誤是構建一個AgentrandomPref,該簽名表示您正在生成一個隨機雙打列表,但您只能生成一個雙精度。我也不是100%確定這個函數應該如何工作,因爲你從不在任何地方使用種子值。你要麼返回一個Rand monad,要麼取一個種子值,並用它來生成一個普通的double。兩者都沒有意義。

這是一個使用種子版本,並返回一個普通的雙

randomPref :: Integer -> Double 
randomPref seed = evalRand (getRandomR (0.0, 1.0)) g where 
    g = mkStdGen (fromIntegral seed) 

我用mkStdGenSystem.Random做爲一個例子,但你可能想用RandomGen其他一些實例來替換它。

然而,上面是一個非常可疑的使用MonadRandom,除非它是真正重要的是產生具有特定種子每個座席,它可能更合乎邏輯的實現randomPref這樣

randomPref :: RandomGen g => Rand g Double 
randomPref = getRandomR (0.0, 1.0) 

現在我們不接受種子價值,我們只是聲明randomPref是一個隨機雙。但是,你不能只使用一個隨機double,因爲它是一個常規double,所以我們也需要改變其他函數。首先,createAgent

createAgent:: RandomGen g => String -> Int -> Rand g Agent 
createAgent name x = Agent name <$> prefs where 
    prefs = replicateM x randomPref 

我們更改簽名,以反映這一事實,我們實際上是返回一個隨機Agent<$>運算符來自模塊Control.Applicative,它用於將預期爲普通值的函數應用於值Rand。這只是編寫fmap (Agent name) prefs的更好方法。

prefs是根據replicateM(來自模塊Control.Monad)定義的,它將單次值複製了x次,因此您可以獲得x個隨機首選項。另一方面,我已將所有功能更改爲使用Int值而不是Integers。除非您真的想要生成數十億的代理,否則這會使代碼更快,並且許多標準庫函數(例如replicateM)僅接受機器整數。

generateAgents:: RandomGen g => Int -> Int -> Rand g AgentSet 
generateAgents n x = mapM (\i -> createAgent (show i) x) [1..n] 

generateAgents以類似的方式改變。我們在簽名中注意到,我們隨機返回AgentSet,並將列表理解改爲mapMmapM與標準map函數類似,只不過它適用於返回一元值的函數(如Rand)。

generateAgentSets:: RandomGen g => Int -> Int -> Int -> Rand g [AgentSet] 
generateAgentSets s n x = replicateM s (generateAgents n x) 

generateAgentSets遵循相同的程序。我們已經取代列表解析與replicateM生成的隨機劑設爲s實例。

最大的變化是必需的在generate功能

generate :: RandomGen g => FilePath -> Int -> Int -> Int -> g -> IO() 
generate fname s n x g = do 
    let randomSets = generateAgentSets s n x 
     agentSets = evalRand randomSets g 
    writeFile fname $ show agentSets 

我們需要一個隨機數發生器,其然後與evalRand用來轉動Rand AgentSet值代入,然後可以寫平原AgentSet值傳遞到磁盤。

爲了更好地理解爲什麼我們需要fmap/<$>以及諸如mapMreplicateM,而不是普通的舊列表解析功能,你可能想讀Learn you a Haskell for Great GoodChapter 11Chapter 12

+0

謝謝你,你是對我的'randomPref'不應該返回一個列表 - 我剛剛發現這個類型簽名'蘭德G [雙]'的地方,並在不知不覺中使用它。而'seed'是舊代碼的遺蹟 - 未使用'MonadRandom' - 事實上,除了'一切randomPref'是不使用'MonadRandom'代碼 - 因爲我只是想弄清楚後就放棄瞭如何使用該死的東西!對困惑感到抱歉。 – drozzy 2012-02-15 13:04:50

+0

當我嘗試使用'產生「out.txt」從控制檯2個10 3 getStdGen'我得到'不實例(RandomGen(IO StdGen))'錯誤:-( – drozzy 2012-02-15 15:23:49

+0

getStdGen返回一元價值,所以你需要做的'getStdGen >> =生成 「out.txt」 2 10 3',或者可替換地,可以使用'生成 「out.txt」 2 10 3(newStdGen種子)'。所不同的是'getStdGen'返回全球,共享隨機數發生器和'newStdGen'創建一個基於種子值一個新的隨機生成。 – shang 2012-02-15 15:36:51