10

我终于掌握了如何使用单子(不知道我是否理解它们......),但我的代码从来都不是很优雅。我想是因为缺乏对所有这些功能如何Control.Monad真正提供帮助的把握。因此,我认为使用 state monad 在特定代码段中询问有关此问题的提示会很好。

代码的目标是计算多种随机游走,这是我在更复杂的事情之前尝试做的事情。问题是我同时有两个有状态的计算,我想知道如何优雅地组合它们:

  1. 更新随机数生成器的函数是某种类型Seed -> (DeltaPosition, Seed)
  2. 更新随机游走器位置的函数是某种类型的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]

我得到了我想要的,这是随机游走者所占据的位置列表。但是......我觉得必须有一种更优雅的方式来做到这一点。我有两个问题:

  1. 我可以使用来自的聪明函数以更“单子”的方式重写这些函数Control.Monad吗?

  2. 有没有可以使用这样的组合状态的通用模式?这是否与单子变压器或类似的东西有关?

4

2 回答 2

11

更新:我应该提到实际上有一个更好的方法来做到这一点,它根本不需要State或单子:

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 中完成所有状态操作,而不是尝试组合 a State Aand State B)在我看来是最优雅的解决方案。


更新:回答你关于使用单子转换器堆叠State单子的问题:这当然是可能的。我们可以这样写,例如:

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版本更清晰一些。

于 2010-07-30T23:11:09.073 回答
1

组合 2 个 monad 的常用方法(也是大多数 monad 的唯一方法)是使用 monad 转换器,但使用不同的Statemonad,您有更多选择。例如:您可以使用以下功能:

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'))
于 2015-10-27T15:04:05.660 回答