2010-07-30 93 views
10

我终于忍住了如何使用monads(不知道我是否理解他们......),但我的代码永远不会很优雅。我想是因为对Control.Monad上所有这些功能如何能够真正帮助的缺乏控制。所以我认为在使用状态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. 是否有关于这样的结合状态的一般模式是可以用吗?这是否与monad变形金刚或类似的东西有关?

+2

顺便说一句,这是避免使用'State'数据的构造,因为在'mtl'的继任者('单子-fd')是一个好主意,' State'是用'StateT'来定义的,因此'State'数据构造函数不存在。 – 2010-07-31 00:53:05

+0

@TravisBrown实际上,'monads-fd'已被弃用,以'mtl'为代价。 (意识到你的评论是5岁。) – crockeea 2015-10-27 15:14:14

回答

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] 

这种方法(做的所有国家操纵一个单子,而不是试图组成一个State AState 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版本更清晰一点。

1

构成2个单子(以及大多数单子的唯一途径)的常用方法是使用单子变压器,但使用不同的State单子可以提供更多选择。例如:你可以使用这些功能:

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'))