2009-12-24 14 views
16

Estoy tratando de captar la mónada de estado y con este propósito quería escribir un código monádico que generaría una secuencia de números aleatorios utilizando un generador congruente lineal (probablemente no es bueno) , pero mi intención es solo aprender la Mónada de Estado, no construir una buena biblioteca de RNG).Mónada de estado, secuencias de números aleatorios y código monádico

El generador es sólo esto (yo quiero para generar una secuencia de Bool s para simplificar):

type Seed = Int 

random :: Seed -> (Bool, Seed) 
random seed = let (a, c, m) = (1664525, 1013904223, 2^32) -- some params for the LCG 
        seed' = (a*seed + c) `mod` m 
       in (even seed', seed') -- return True/False if seed' is even/odd 

no se preocupe por los números, esto es sólo una regla de actualización para la semilla que (según a Recetas Numéricas) debería generar una secuencia pseudoaleatoria de Int s. Ahora, si quiero generar números aleatorios de forma secuencial que haría:

rand3Bools :: Seed -> ([Bool], Seed) 
rand3Bools seed0 = let (b1, seed1) = random seed0 
         (b2, seed2) = random seed1 
         (b3, seed3) = random seed2 
        in ([b1,b2,b3], seed3) 

Ok, por lo que podría evitar esta repetitivo mediante el uso de una Mónada Estado:

import Control.Monad.State 

data Random {seed :: Seed, value :: Bool} 

nextVal = do 
    Random seed val <- get 
    let seed' = updateSeed seed 
     val' = even seed' 
    put (Random seed' val') 
    return val' 

updateSeed seed = let (a,b,m) = (1664525, 1013904223, 2^32) in (a*seed + c) `mod` m 

Y, por último:

getNRandSt n = replicateM n nextVal 

getNRand :: Int -> Seed -> [Bool] 
getNRand n seed = evalState (getNRandStates n) (Random seed True) 

Ok, esto funciona bien y dame una lista de n pseudoaleatorios Bool s para cada semilla dada. Pero ...

Puedo leer lo que he hecho (basado principalmente en este ejemplo: http://www.haskell.org/pipermail/beginners/2008-September/000275.html) y replicarlo para hacer otras cosas. Pero no creo que pueda entender lo que realmente está sucediendo detrás de la notación do y las funciones monádicas (como replicateM).

¿Alguien me puede ayudar con algunas de estas dudas?

1 - He tratado de desugar la función nextVal para entender lo que hace, pero no pude. Adivino que extrae el estado actual, lo actualiza y luego pasa el estado al siguiente cálculo, pero esto solo se basa en leer este do-azúcar como si fuera inglés.

¿Cómo deshago realmente esta función a las funciones >> = y return originales paso a paso?

2 - No pude comprender qué es exactamente lo que hacen las funciones put y get. Puedo adivinar que "empacan" y "descomprimen" el estado. Pero la mecánica detrás del do-azúcar aún me resulta esquiva.

Bien, cualquier otra observación general sobre este código es muy bienvenida. A veces me enamoré de Haskell de que puedo crear un código que funcione y haga lo que espero que haga, pero no puedo "seguir la evaluación" como estoy acostumbrado a hacer con los programas imperativos.

Respuesta

30

La mónada de estado parece un poco confusa al principio; hagamos lo que sugirió Norman Ramsey, y repasemos cómo implementarlo desde cero. Advertencia, esto es bastante largo!

En primer lugar, Estado tiene dos parámetros de tipo: el tipo de los datos de estado contenidas y el tipo de la final resultado del cálculo. Utilizaremos stateData y result respectivamente como variables de tipo aquí. Esto tiene sentido si lo piensas; la característica definitoria de un cálculo basado en el estado es que modifica un estado mientras produce un resultado.

menos obvio es que el tipo de constructor toma una funciónde un estado a un estado y el resultado modificado, así:

newtype State stateData result = State (stateData -> (result, stateData)) 

Así, mientras que la mónada se llama "Estado", el valor real envuelta por la mónada es el de un cálculo basado en estado, no el valor real del estado contenido.

Teniendo esto en cuenta, no debería sorprendernos que la función runState utilizada para ejecutar un cálculo en la mónada de estado en realidad no sea más que un descriptor de acceso para la propia función envolvente, y podría definirse así:

runState (State f) = f 

Entonces, ¿qué significa cuando define una función que devuelve un valor de estado? Vamos a ignorar por un momento el hecho de que el Estado es una mónada, y solo mirar los tipos subyacentes. En primer lugar, tenga en cuenta esta función (que en realidad no hace nada con el estado):

len2State :: String -> State Int Bool 
len2State s = return ((length s) == 2) 

Si nos fijamos en la definición de Estado, podemos ver que aquí el tipo stateData es Int, y el tipo es resultBool, por lo que la función envuelta por el constructor de datos debe tener el tipo Int -> (Bool, Int). Ahora imagine una versión sin estado de len2State, obviamente, tendría el tipo String -> Bool. Entonces, ¿cómo haría para convertir esa función en una que devuelva un valor que encaje en un contenedor de estado?

Bueno, obviamente, la función convertida necesitará tomar un segundo parámetro, un Int que representa el valor del estado. También necesita devolver un valor de estado, otro Int. Como en realidad no estamos haciendo nada con el estado en esta función, hagamos lo más obvio: pasar esa int por completo. Aquí está una función en forma de Estado, se define en términos de la versión menos Estado:

len2 :: String -> Bool 
len2 s = ((length s) == 2) 

len2State :: String -> (Int -> (Bool, Int)) 
len2State s i = (len2' s, i) 

Pero eso es un poco tonto y redundante. Vamos a generalizar la conversión para que podamos pasar el valor del resultado y convertir cualquier cosa en una función de estado.

convert :: Bool -> (Int -> (Bool, Int)) 
convert r d = (r, d) 

len2 s = ((length s) == 2) 

len2State :: String -> (Int -> (Bool, Int)) 
len2State s = convert (len2 s) 

¿Qué sucede si queremos una función que cambie el estado? Obviamente no podemos construir uno con convert, ya que escribimos eso para pasar el estado. Vamos a mantenerlo simple, y escribir una función para sobrescribir el estado con un nuevo valor. ¿Qué tipo de tipo necesitaría? Necesitará un Int para el nuevo valor de estado y, por supuesto, tendrá que devolver una función stateData -> (result, stateData), porque eso es lo que necesita nuestro contenedor de estado. Sobrescribir el valor de estado realmente no tiene un valor sensible de result fuera del cálculo estatal, por lo que nuestro resultado aquí será simplemente (), la tupla de elemento cero que representa "sin valor" en Haskell.

overwriteState :: Int -> (Int -> ((), Int)) 
overwriteState newState _ = ((), newState) 

¡Eso fue fácil! Ahora, en realidad hagamos algo con los datos de estado. Vamos a reescribir len2State de arriba en algo más sensato: vamos a comparar la longitud de la cadena con el valor de estado actual.

lenState :: String -> (Int -> (Bool, Int)) 
lenState s i = ((length s) == i, i) 

¿Podemos generalizar esto en un convertidor y una función sin estado, como lo hicimos antes? No tan fácil. Nuestra función len deberá tomar el estado como argumento, pero no queremos que "sepa sobre" el estado. Torpe, de hecho. Sin embargo, podemos escribir una función de ayuda rápida que maneje todo por nosotros: le daremos una función que necesita usar el valor de estado, y pasará el valor y luego volverá a empaquetar todo en una función con forma de estado. dejando len ninguno más sabio.

useState :: (Int -> Bool) -> Int -> (Bool, Int) 
useState f d = (f d, d) 

len :: String -> Int -> Bool 
len s i = (length s) == i 

lenState :: String -> (Int -> (Bool, Int)) 
lenState s = useState (len s) 

Ahora, la parte difícil - ¿y si queremos unir estas funciones? Digamos que queremos usar lenState en una cadena, luego doblamos el valor del estado si el resultado es falso, luego revisamos nuevamente la cadena y finalmente devolvemos "verdadero" si alguna de las comprobaciones lo hizo. Tenemos todas las partes que necesitamos para esta tarea, pero escribirlo sería un dolor. ¿Podemos hacer una función que encadena automáticamente dos funciones que cada función regresa de estado funciona? ¡Cosa segura! Solo debemos asegurarnos de que toma como argumentos dos cosas: la función State devuelta por la primera función y una función que toma el tipo result de la función anterior como argumento. Vamos a ver cómo resulta:

chainStates :: (Int -> (result1, Int)) -> (result1 -> (Int -> (result2, Int))) -> (Int -> (result2, Int)) 
chainStates prev f d = let (r, d') = prev d 
         in f r d' 

Todo esto está haciendo es aplicar la primera función de estado a algunos datos de estado, a continuación, aplicar la segunda función para el estado de los datos modificados resultado y. Simple, ¿verdad?

Ahora, la parte interesante: entre chainStates y convert, ¡deberíamos poder convertir cualquier combinación de funciones sin estado en una función habilitada por estado! Lo único que necesitamos ahora es un reemplazo para useState que devuelva los datos de estado como resultado, para que los chainStates puedan pasarlo a las funciones que no saben nada sobre el truco que estamos utilizando.Además, usaremos lambdas para aceptar el resultado de las funciones anteriores y darles nombres temporales. Está bien, vamos a hacer que esto suceda:

extractState :: Int -> (Int, Int) 
extractState d = (d, d) 

chained :: String -> (Int -> (Bool, Int)) 
chained str = chainStates extractState   $ \state1 -> 
       let check1 = (len str state1) in 
       chainStates (overwriteState (
        if check1 
        then state1 
        else state1 * 2))    $ \ _ -> 
       chainStates extractState   $ \state2 -> 
       let check2 = (len str state2) in 
       convert (check1 || check2) 

y probarlo:

> chained "abcd" 2 
(True, 4) 
> chained "abcd" 3 
(False, 6) 
> chained "abcd" 4 
(True, 4) 
> chained "abcdef" 5 
(False, 10) 

Por supuesto, no podemos olvidar que el Estado es en realidad una mónada que envuelve las funciones propias del Estado y nos mantiene lejos de ellos, por lo que ninguna de nuestras ingeniosas funciones que hemos construido nos ayudará con lo real. ¿O lo harán? En un giro sorprendente, resulta que la mónada estado real proporciona las mismas funciones, bajo diferentes nombres:

runState (State s) = s 
return r = State (convert r) 
(>>=) s f = State (\d -> let (r, d') = (runState s) d in 
         runState (f r) d') 
get = State extractState 
put d = State (overwriteState d) 

nota que >> = es casi idéntica a chainStates, pero no había una buena manera de definirlo usando chainStates. Por lo tanto, para envolver las cosas, podemos reescribir el ejemplo final utilizando el Estado verdadera:

chained str = get        >>= \state1 -> 
       let check1 = (len str state1) in 
       put (if check1 
        then state1 else state1 * 2) >>= \ _ -> 
       get        >>= \state2 -> 
       let check2 = (len str state2) in 
       return (check1 || check2) 

O, todo confitada con el equivalente a la notación do:

chained str = do 
     state1 <- get 
     let check1 = len str state1 
     _ <- put (if check1 then state1 else state1 * 2) 
     state2 <- get 
     let check2 = (len str state2) 
     return (check1 || check2) 
+1

que fue un placer leer – barkmadley

+2

Gracias, fue un placer escribir también, aunque probablemente podría haber usado más trabajo en algunos lugares. Estaba preocupado de que las personas se hubieran desanimado por la duración, pero supongo que no ... –

7

En primer lugar, su ejemplo es demasiado complicado porque no necesita almacenar el val en la mónada de estado; solo la semilla es el estado persistente. En segundo lugar, creo que tendrás más suerte si en lugar de utilizar la mónada de estado estándar, usted vuelve a implementar todas las mónadas de estado y sus operaciones, con sus tipos. Creo que aprenderás más de esta manera.Aquí hay un par de declaraciones para empezar:

data MyState s a = MyState (s -> (s, b)) 

get :: Mystate s s 
put :: s -> Mystate s() 

entonces usted puede escribir sus propios conectivas:

unit :: a -> Mystate s a 
bind :: Mystate s a -> (a -> Mystate s b) -> Mystate s b 

Finalmente

data Seed = Seed Int 
nextVal :: Mystate Seed Bool 

En cuanto a su desugaring problemas, la notación do que está utilizando es bastante sofisticada. Pero desugaring es un procedimiento mecánico de línea por vez. Lo más cerca que he podido averiguar, el código debe desugar como esto (que se remonta a los tipos de originales y el código, lo que no estoy de acuerdo con):

nextVal = get >>= \ Random seed val -> 
         let seed' = updateSeed seed 
          val' = even seed' 
         in put (Random seed' val') >>= \ _ -> return val' 

Con el fin de hacer que la estructura de anidación un poco más claro, I' he tomado mayores libertades con la sangría.

4

Tienes una un par de excelentes respuestas. Lo que hago cuando trabajo con la mónada estatal es en mi mente reemplazar State s a con s -> (s,a) (después de todo, eso es realmente).

A continuación, obtener un tipo de lazo que se parece a:

(>>=) :: (s -> (s,a)) -> 
     (a -> s -> (s,b)) -> 
     (s -> (s,b)) 

y ves que se unen es sólo un tipo especializado de la función de operador de composición, como (.)

escribí un blog/tutorial sobre la mónada estatal here. Probablemente no sea particularmente bueno, pero me ayudó a asimilar las cosas un poco mejor escribiéndolas.