如何重构代码以使用MonadRandom [英] How to refactor code to use MonadRandom
问题描述
我正在尝试使用 MonadRandom
。我把它放到了 randomPref
函数中,但是整个事情在之后都会出现!
module AgentGenerator其中
导入System.Random
导入数据.Hashable
import Control.Monad.Random
导入System.Environment
- 生成代理并写入文件
- 'fname' - 输出文件名
- 's' - 代理集数量
- 'n' - 集合中代理的数量
- 'x' - 每个代理的首选项数量
生成fname snx = do
writeFile fname $ show $ generateAgentSets snx
- Agent类型:Name,首选项列表
data Agent = Agent String [Double]派生Show
类型AgentSet = [Agent]
- 每个生成'''代理'集合','x'首选项每个
generateAgentSets :: Integer - >整数 - >整数 - > [AgentSet]
generateAgentSets s n x = [generateAgents n x | i< - [1..s]]
- 使用'x'首选项生成n个代理每个
generateAgents :: Integer - >整数 - > AgentSet
generateAgents n x = [createAgent(show i)x | i< - [1..n]]
- 使用'x'首选项创建代理'名称'
createAgent :: String - >整数 - > Agent
createAgent name x =代理名称prefs其中
prefs = [randomPref(i + hashed)| i < - [1..x]]其中
hashed = fromIntegral(散列名称)
- 基于种子$ b $在[0,1]之间生成单个随机值b - TODO:摆脱种子的东西,用MonadRandom代替
randomPref ::(RandomGen g)=>整数 - > Rand g [Double]
randomPref seed = getRandomR(0.0,1.0)
您已将代理
定义为
data Agent = Agent String [Double]
但是在 createAgent
,你正试图使用类型构造一个 Agent
代理字符串[Rand g [Double]]
另一类错误是在 randomPref
,签名表示您正在生成一个随机双打列表,但您只生成一个双精度值。我也不是100%确定这个函数应该如何工作,因为你从不在任何地方使用种子值。你要么返回一个 Rand
monad或者取一个种子值并用它来产生一个普通的double。两者都没有意义。
这是一个使用种子的版本,并且返回一个普通的double
randomPref :: Integer - > Double
randomPref seed = evalRand(getRandomR(0.0,1.0))g其中
g = mkStdGen(fromIntegral seed)
我以 System.Random
为例,使用 mkStdGen
作为例子,但是您可能希望用其他一些 RandomGen
。
的其他实例替换它。但是,上述内容是一个相当可疑的用法 MonadRandom
除非用特定的种子生成每个代理真的很重要,否则实现 randomPref
可能更符合逻辑
randomPref :: RandomGen g => Rand g Double
randomPref = getRandomR(0.0,1.0)
现在我们不采取在种子值中,我们只是声明 randomPref
是一个随机double。但是,你不能只使用一个随机double,因为它是一个常规double,所以我们也需要改变其他函数。首先, createAgent
createAgent :: RandomGen g =>字符串 - > Int - >代理
createAgent名称x =代理名称< $> prefs其中
prefs = replicateM x randomPref
我们更改签名以反映我们实际上是返回一个随机的 Agent
。 < $>
运算符来自模块 Control.Applicative
,它用于应用期待a普通值为 Rand
值。这只是编写 fmap(代理名称)前缀
的更好方法。
prefs
是根据 replicateM
(来自模块Control.Monad)定义的,它将一次性值复制x次,因此您可以获得x个随机首选项。另一方面,我已经将所有函数改为使用 Int
值而不是整数
。除非您真的想要生成数十亿代理,否则这会使代码更快,并且许多标准库函数(如 replicateM
)仅接受机器整数。
generateAgents :: RandomGen g => Int - > Int - > Rand g AgentSet
generateAgents nx = mapM(\ i - > createAgent(show i)x)[1..n]
generateAgents
以类似方式更改。我们在签名中注意到,我们返回一个随机 AgentSet
,并将列表理解改为 mapM
。 mapM
与标准的 map
函数类似,只不过它适用于返回一元值的函数(比如<$ c
generateAgentSets :: RandomGen g => Int - > Int - > Int - > Rand g [AgentSet]
generateAgentSets snx = replicateM s(generateAgents nx)
generateAgentSets
遵循相同的例程。我们用 replicateM
替换了列表理解来生成随机代理集合的实例。
最大的改变是在 generate
函数中需要
generate :: RandomGen g => FilePath - > Int - > Int - > Int - > g - > IO()
生成fname snxg = do
let randomSets = generateAgentSets snx
agentSets = evalRand randomSets g
writeFile fname $ show agentSets
我们需要传入一个随机数生成器,然后与 evalRand
一起使用来转换 Rand AgentSet
值转换为可以写入磁盘的普通 AgentSet
值。
为了更好地理解为什么我们需要 fmap
/ < $>
作为 mapM
和 replicateM
而不是普通的旧列表解析,您可能需要阅读第11章和 Chapter 12 from Learn you Gre的Haskell很好。
I am trying to use MonadRandom
. I put it into randomPref
function, but the whole thing brows up afterwards! Any tips are appreciated.
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)
You've defined Agent
as
data Agent = Agent String [Double]
But in createAgent
, you are trying to construct an Agent
using the types
Agent String [Rand g [Double]]
Another type error is that in randomPref
, the signature says that you are generating a list of random doubles, but you only generate one double. I'm also not 100% sure how the function should work, given that you never use the seed value anywhere. You either want to return a Rand
monad or take a seed value and use that to generate a plain double. Having both doesn't really make sense.
Here's a version that uses a seed, and returns a plain double
randomPref :: Integer -> Double
randomPref seed = evalRand (getRandomR (0.0, 1.0)) g where
g = mkStdGen (fromIntegral seed)
I've used mkStdGen
from System.Random
here as an example, but you might want to replace it with some other instance of RandomGen
.
However, the above is a pretty questionable use of MonadRandom
and unless it's really important to generate each agent with a specific seed, it's probably more logical to implement randomPref
like this
randomPref :: RandomGen g => Rand g Double
randomPref = getRandomR (0.0, 1.0)
Now we don't take in a seed value, we just declare that randomPref
is a random double. However, you can't just use a random double like it was a regular double, so we need to change the other functions too. First, createAgent
createAgent:: RandomGen g => String -> Int -> Rand g Agent
createAgent name x = Agent name <$> prefs where
prefs = replicateM x randomPref
We change the signature to reflect the fact that we are actually returning a random Agent
. The <$>
operator is from the module Control.Applicative
and it is used to apply a function expecting a plain value to a Rand
value. This is just a nicer way to write fmap (Agent name) prefs
.
prefs
is defined in terms of replicateM
(from the module Control.Monad), which replicates a monadic value x times, so you get x random prefs. On another note, I've changed all the functions to use Int
values instead of Integers
. Unless you really want to generate billions of agents, this makes the code a lot faster, and many standard library functions (like replicateM
) only accept machine ints.
generateAgents:: RandomGen g => Int -> Int -> Rand g AgentSet
generateAgents n x = mapM (\i -> createAgent (show i) x) [1..n]
generateAgents
is changed in a similar way. We note in the signature that we are returning a random AgentSet
, and change the list comprehension into mapM
. mapM
is like the standard map
function, except that is works with functions that return monadic values (such as Rand
).
generateAgentSets:: RandomGen g => Int -> Int -> Int -> Rand g [AgentSet]
generateAgentSets s n x = replicateM s (generateAgents n x)
generateAgentSets
follows the same routine. We've replaced the list comprehension with replicateM
to generate s instances of random agent sets.
The biggest change is required in the generate
function
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
We need to pass in a random number generator, which is then used with evalRand
to turn the Rand AgentSet
values into plain AgentSet
values that can then be written to disk.
To get a better understanding why we need fmap
/<$>
and functions such as mapM
and replicateM
instead of plain old list comprehensions, you might want to read Chapter 11 and Chapter 12 from Learn you a Haskell for Great Good.
这篇关于如何重构代码以使用MonadRandom的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!