我终于掌握了如何使用monads(不知道我是否理解它们......),但我的代码从来都不是很优雅。我想是因为缺乏对Control.Monad
上所有这些功能如何真正帮助的把握。所以我认为在使用状态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
中的聪明功能以更“单一”的方式重写这些功能吗?
是否存在可以使用的组合状态的一般模式?这与monad变形金刚有什么关系吗?
答案 0 :(得分:11)
更新:我应该提到实际上有一种更好的方法可以完全不需要State
或monad:
takeStep :: (Double, StdGen) -> (Double, StdGen)
takeStep (p, g) = let (d, g') = random g in (p + d, g')
takeSteps n = take n . tail . map fst $ iterate takeStep (0, mkStdGen 0)
它按预期工作:
*Main> takeSteps 5
[0.9872770354820595,0.9882724161698186,1.9620425108498993,2.0923229488759123,2.296045158010918]
如果你没有致力于“编写”两个独立的有状态计算,你可以更直接地完成同样的事情:
takeStep :: State (Double, StdGen) Double
takeStep = do
(pos, gen) <- get
let (delta, gen') = random gen
let pos' = pos + delta
put (pos', gen')
return pos'
takeSteps n = evalState (replicateM n takeStep) (0, mkStdGen 0)
这会产生与您的示例相同的输出:
*Main> takeSteps 5
[0.9872770354820595,0.9882724161698186,1.9620425108498993,2.0923229488759123,2.296045158010918]
这种方法(在单个monad中执行所有状态操作而不是尝试编写State A
和State B
)在我看来就像是最优雅的解决方案。
更新:要回答关于使用monad变换器来堆叠State
monad的问题:这当然是可能的。我们可以编写以下内容,例如:
update' :: (Monad m) => Double -> StateT Double m Double
update' x = StateT $ \y -> let z = x + y in return (z, z)
generate' :: (Monad m) => StateT StdGen m Double
generate' = StateT $ return . random
takeStep' :: StateT Double (State StdGen) Double
takeStep' = update' =<< lift generate'
takeSteps' n = evalState (evalStateT (replicateM n takeStep') 0) $ mkStdGen 0
我们也可以按相反的顺序进行堆叠。
此版本再次生成相同的输出,但在我看来,非StateT
版本更清晰一点。
答案 1 :(得分:1)
编写2个monad(以及大多数monad的唯一方法)的常用方法是使用monad变换器,但使用不同的State
monad可以有更多选项。例如:您可以使用以下功能:
leftState :: State a r -> State (a,b) r
leftState act = state $ \ ~(a,b) -> let
(r,a') = runState act a
in (r,(a',b))
rightState :: State b r -> State (a,b) r
rightState act = state $ \ ~(a,b) -> let
(r,b') = runState act b
in (r,(a,b'))