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.
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 result
Bool
, 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)
que fue un placer leer – barkmadley
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 ... –