2010-07-30 24 views
10

Finalmente me di cuenta de cómo usar las mónadas (no sé si las entiendo ...), pero mi código nunca es muy elegante. Supongo que es por la falta de control sobre cómo todas esas funciones en Control.Monad realmente pueden ayudar. Así que pensé que sería bueno pedir consejos sobre esto en un código en particular usando la mónada estatal.¿Consejos para un código más elegante con mónadas?

El objetivo del código es calcular muchos tipos de paseos aleatorios, y es algo que intento hacer antes de algo más complicado. El problema es que tengo dos cómputos con estado, al mismo tiempo, y me gustaría saber cómo componer con elegancia:

  1. la función que actualiza el generador de números aleatorios es algo del tipo Seed -> (DeltaPosition, Seed)
  2. La función que actualiza la posición del caminador aleatorio es algo del tipo DeltaPosition -> Position -> (Log, Position) (donde Log es solo una forma de informar cuál es la posición actual del caminador aleatorio).

Lo que he hecho es lo siguiente:

Tengo una función para componer estos dos cómputos con estado:

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

y entonces convertirlo en una función que componen los estados:

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 

Y luego tengo la cosa más simple, por ejemplo, un andador aleatorio que sumará un número aleatorio a su posición actual:

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 

y una función para hacer esto en varias ocasiones:

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

Y luego, si me carga esta en ghci y corro:

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

consigo lo que quiero, que es una lista de las posiciones ocupadas por el caminante al azar. Pero ... Siento que debe haber una manera más elegante de hacer esto. Tengo dos preguntas:

  1. ¿Puedo reescribir las funciones de una manera más "monádico", utilizando las funciones inteligentes de Control.Monad?

  2. ¿Existe un patrón general sobre cómo combinar estados como este que se pueden usar? ¿Tiene esto algo que ver con los transformadores de mónada o algo así?

+2

Por cierto, es una buena idea evitar el uso del 'State' constructor de datos, ya que en el sucesor de' mtl' ('mónadas-fd'),' State' se define en términos de 'StateT' y, por lo tanto, el constructor de datos' State' no existe. –

+0

@TravisBrown En realidad, 'monads-fd' está en desuso en favor de' mtl'. (Reconociendo que su comentario tiene 5 años) – crockeea

Respuesta

11

Actualización: Me debería haber mencionado que hay realmente una manera mucho más agradable de hacer esto que no requiere State o mónadas en todo:

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) 

que funciona como se desea:

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

Si no está comprometido con la idea de "componer" dos cómputos estadísticos separados, puede lograr la misma cosa mucho más directa:

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) 

Esto produce el mismo resultado que el ejemplo:

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

Este enfoque (haciendo toda la manipulación del estado en una sola mónada en lugar de tratar de componer una State A y State B) me parece la solución más elegante.


Actualización: Para responder a su pregunta sobre el uso de transformadores monad apilar State mónadas: es ciertamente posible. Podemos escribir la siguiente, por ejemplo:

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 

También podríamos hacer el apilamiento en orden inverso.

Esta versión produce de nuevo la misma salida, pero en mi opinión la versión no StateT es un poco más claro.

1

La forma habitual de componer 2 mónadas (y la única forma para la mayoría de las mónadas) es con transformadores de mónada, pero con diferentes mónadas State tiene más opciones. Por ejemplo: usted podría utilizar estas funciones:

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