我终于掌握了如何使用单子(不知道我是否理解它们......),但我的代码从来都不是很优雅。我想是因为缺乏对所有这些功能如何Control.Monad
真正提供帮助的把握。因此,我认为使用 state monad 在特定代码段中询问有关此问题的提示会很好。
代码的目标是计算多种随机游走,这是我在更复杂的事情之前尝试做的事情。问题是我同时有两个有状态的计算,我想知道如何优雅地组合它们:
- 更新随机数生成器的函数是某种类型
Seed -> (DeltaPosition, Seed)
- 更新随机游走器位置的函数是某种类型的
DeltaPosition -> Position -> (Log, Position)
(Log
我可以通过某种方式报告随机游走器的当前位置)。
我所做的是这样的:
我有一个函数来组成这两个有状态的计算:
composing :: (g -> (b, g)) -> (b -> s -> (v,s)) -> (s,g) -> (v, (s, g))
composing generate update (st1, gen1) = let (rnd, gen2) = generate gen1
(val, st2) = update rnd st1
in (val, (st2, gen2))
然后我把它变成一个组成状态的函数:
stateComposed :: State g b -> (b -> State s v) -> State (s,g) v
stateComposed rndmizer updater = let generate = runState rndmizer
update x = runState $ updater x
in State $ composing generate update
然后我有一个最简单的东西,例如,一个随机游走器,它只会将一个随机数加到它的当前位置:
update :: Double -> State Double Double
update x = State (\y -> let z = x+y
in (z,z))
generate :: State StdGen Double
generate = State random
rolling1 = stateComposed generate update
以及重复执行此操作的功能:
rollingN 1 = liftM (:[]) rolling1
rollingN n = liftM2 (:) rolling1 rollings
where rollings = rollingN (n-1)
然后,如果我将其加载ghci
并运行:
*Main> evalState (rollingN 5) (0,mkStdGen 0)
[0.9872770354820595,0.9882724161698186,1.9620425108498993,2.0923229488759123,2.296045158010918]
我得到了我想要的,这是随机游走者所占据的位置列表。但是......我觉得必须有一种更优雅的方式来做到这一点。我有两个问题:
我可以使用来自的聪明函数以更“单子”的方式重写这些函数
Control.Monad
吗?有没有可以使用这样的组合状态的通用模式?这是否与单子变压器或类似的东西有关?