如何重构代码以使用MonadRandom

时间:2012-02-15 03:40:19

标签: haskell

我正在尝试使用MonadRandom。我把它放进了randomPref函数中,但事后却完成了整个事情!任何提示都表示赞赏。

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)

1 个答案:

答案 0 :(得分:10)

您已将Agent定义为

data Agent = Agent String [Double]

但在createAgent中,您尝试使用类型

构建Agent
Agent String [Rand g [Double]]

另一个类型错误是在randomPref中,签名表示您正在生成随机双打列表,但您只生成一个双精度数。考虑到你从不在任何地方使用种子值,我也不是100%确定函数应该如何工作。您要么返回Rand monad,要么取种子值并使用它来生成普通双精度值。两者都没有意义。

这是一个使用种子的版本,并返回一个普通的双

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

我在mkStdGen使用System.Random作为示例,但您可能希望将其替换为RandomGen的其他实例。

然而,以上是MonadRandom的一个非常值得怀疑的问题,除非用特定的种子生成每个代理非常重要,否则实现randomPref这样可能更合乎逻辑

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

现在我们没有接受种子值,我们只是声明randomPref是一个随机的双倍。但是,你不能只使用一个随机的双倍,因为它是一个普通的双,所以我们也需要改变其他功能。首先,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)定义的,它复制了monadic值x次,因此你得到x个随机prefs。另外,我已将所有函数更改为使用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函数,但它适用于返回monadic值的函数(例如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等功能而不是简单的旧列表推导,您可能需要阅读{来自Chapter 11的{3}}和Chapter 12