2010-07-30 91 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'))