2010-07-30 20 views
10

Ich habe endlich gelernt, wie man Monaden benutzt (weiß nicht, ob ich sie verstehe ...), aber mein Code ist nie sehr elegant. Ich schätze aus mangelnder Griffigkeit, wie all diese Funktionen auf Control.Monad wirklich helfen können. Also dachte ich, es wäre schön, in einem bestimmten Code mit der State-Monade nach Tipps zu fragen.Tipps für eleganter Code mit Monaden?

Das Ziel des Codes ist es, viele Arten von zufälligen Wanderungen zu berechnen, und es ist etwas, was ich vor etwas komplizierter zu tun versuche. Das Problem ist, dass ich zwei Stateful Berechnungen zur gleichen Zeit haben, und ich würde gerne wissen, wie sie mit Eleganz zu komponieren:

  1. Die Funktion, die den Zufallszahlengenerator aktualisiert etwas vom Typ Seed -> (DeltaPosition, Seed)
  2. Die Funktion, die die Position des Random Walker aktualisiert, ist etwas vom Typ DeltaPosition -> Position -> (Log, Position) (wobei Log nur eine Möglichkeit für mich ist, zu berichten, was die aktuelle Position des Random Walker ist).

Was ich getan habe, ist dies:

ich eine Funktion haben diese beiden Stateful Berechnungen zu komponieren:

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

und dann habe ich es in eine Funktion drehen, dass die Staaten zusammensetzen:

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 

Und dann habe ich die einfachste Sache, zum Beispiel, ein zufälliger Walker, der nur eine zufällige Zahl an seine aktuelle Position summieren wird:

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 

und eine Funktion dies wiederholt zu tun:

rollingN 1 = liftM (:[]) rolling1 
rollingN n = liftM2 (:) rolling1 rollings 
    where rollings = rollingN (n-1) 

Und dann, wenn ich diese laden in ghci und laufen:

*Main> evalState (rollingN 5) (0,mkStdGen 0) 
[0.9872770354820595,0.9882724161698186,1.9620425108498993,2.0923229488759123,2.296045158010918] 

ich bekommen, was ich will, was eine ist Liste der vom Random Walker belegten Positionen. Aber ... Ich denke, es muss einen eleganteren Weg geben, dies zu tun. Ich habe zwei Fragen:

  1. Kann ich diese Funktionen in einer „monadischen“ Art und Weise neu zu schreiben, mit cleveren Funktionen von Control.Monad?

  2. Gibt es ein allgemeines Muster für die Kombination solcher Zustände, die verwendet werden können? Hat das etwas mit Monad-Transformatoren oder etwas ähnlichem zu tun?

+2

By the way, ist es eine gute Idee, um das 'State' Daten Konstruktor zu vermeiden, verwenden, da in' mtl' Nachfolgern ('Monaden-fd'),' State ist definiert als "StateT" und daher existiert der "State" -Datenkonstruktor nicht. –

+0

@TravisBrown Tatsächlich ist 'monads-fd' für 'mtl' veraltet. (Erkennen, dass Ihr Kommentar 5 Jahre alt ist.) – crockeea

Antwort

11

Update: Ich habe zu erwähnen, dass es tatsächlich eine viel schönere Art und Weise, dies zu tun, die nicht State oder Monaden überhaupt benötigt:

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) 

es wie gewünscht funktioniert:

*Main> takeSteps 5 
[0.9872770354820595,0.9882724161698186,1.9620425108498993,2.0923229488759123,2.296045158010918] 

Wenn Sie sich nicht der Idee verschrieben haben, zwei separate Stateful-Berechnungen zu erstellen, können Sie dies erreichen die gleiche Sache viel mehr ohne weiteres:

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) 

Dies erzeugt die gleiche Ausgabe wie Ihr Beispiel:

*Main> takeSteps 5 
[0.9872770354820595,0.9882724161698186,1.9620425108498993,2.0923229488759123,2.296045158010918] 

Dieser Ansatz (alles staatliche Manipulation in einem einzigen Monade zu tun, anstatt ein State A zu komponieren, zu versuchen und State B) scheint mir die eleganteste Lösung zu sein.


Update: Um Ihre Frage über die Verwendung von Monade Transformatoren zu beantworten State Monaden zu stapeln: es sicherlich möglich ist. Wir können folgendes schreiben, zum Beispiel:

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 

Wir könnten auch die Stapelung in umgekehrter Reihenfolge machen.

Diese Version produziert wieder die gleiche Ausgabe, aber meiner Meinung nach ist die nicht StateT Version ein bisschen klarer.

1

Die übliche Weise, 2 Monaden zu bilden (und der einzige Weg für die meisten Monaden), ist mit Monadtransformatoren, aber mit unterschiedlichen State Monaden haben Sie mehr Wahlen. Zum Beispiel: Sie diese Funktionen nutzen können:

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