Se dice que con Haskell puedes hacer todo lo que un lenguaje hace (nananana), pero tarde o temprano como programador de elementos mutables (ejemplo: quiero que esta variable valga X y se almacene su valor cuando se llame a una determinada función) vemos que esto funciona pero de forma muy enrevesada.
Ejemplo, esto apenas costaría hacerlo en cualquier otro lenguaje:
> mostrar clientes
[]
> añadir cliente Pepito
> mostrar clientes
[Pepito]
Para ello está la mónada State
y StateT
, (ahora comentaré las diferencias).
Y antes debemos entender qué es una mónada:
estructura que representa cálculos definidos como una secuencia de pasos.
Ignoremos por un momento la teoría de categorías.
¿Qué quiere decir? Que los estados en Haskell se podrían componer de tuplas binarias que se pasan determinando el resultado de una función más el estado en el que se quedan. Quedaría algo como añadirClientes c cs = ((), c : cs)
Espera, entiendo lo de c : cs
pero ¿por qué el primer elemento de la tupla es ()
? Pues porque la función añadirClientes
no tendría que devolver nada. En Java por ejemplo la función análoga devolvería un void
.
Bueno, entonces todo ese proceso de gestión de estados, que si sólo quiero el estado final, que si quiero el resultado, que si quiero modificarlo, que si quiero componer con varios estados añadiendo y quitando clientes. Pues todo eso se puede abstraer con la mónada State/StateT.
Si tienen tiempo y ganas pueden ver cómo funciona por dentro State
Entonces la mónada, que lo hace todo por debajo, que te abstrae de esos cálculos, de llevar explícitamente el estado ya no es necesario. Lo hace por debajo de la mesa. Si quieres puedes volver a leer la definición de mónada. Igual pasa con otras mónadas importantes como la famosa Maybe, Read, IO...
¿Cómo quedaría añadirClientes :: Cliente -> Clientes -> ((), Clientes)
con State
?
Pues de esta manera:
type Clientes = [Cliente]
añadirClientes :: Cliente -> State Clientes ()
añadirClientes cliente = do
{-
clientesTotales <- get
put (cliente : clientesTotate)
}-
-- O lo que es lo mismo
modify (\clientes -> cliente : clientes)
Un momento y ¿eso de StateT
?
Es otra vuelta de tuerca más, es el transformador de mónada de estados? .... The state monad transformer. Bueno, resulta que usando State solamente sólo puedes trabajar con la mónada estado, pero igual quieres también trabajar con Maybe o con IO. ¿Quieres una función que trabaje con estados e imprima por pantalla? vas a tener que usar StateT que al contrario de State se construye de la siguiente manera (s -> m (a, s)) -> StateT s m a
Donde m
es IO
. Ejemplo:
-- ahora no tendremos como argumento un cliente
-- se lo pediremos al usuario
añadirClientes :: StateT Clientes IO ()
añadirClientes = do
lift putStrLn "¿A quién deseas añadir como cliente?"
cliente_str <- lift getLine
clientes <- get
return (cliente_str : clientes)
Espera, ¿por qué lift
, y por qué return
? Usaremos lift
para elevar desde IO hasta la mónada que la está envolviendo. (Esto queda un poco flojo, animo a alguien para explicarlo mejor...) Y la función que return
envuelve nuestro estado a cómo lo devolvería StateT.
¿Cómo llamaríamos esto desde nuestra función main :: IO
pues con runStateT
seguido de la función añadirClientes
junto con el estado inicial.
main = do
runStateT añadirClientes []
Otro ejemplo también:
estadoInicial = ["Pepito", "Fulanita"]
main = do
runStateT ( do
añadirClientes
borrarClientes
añadirClientes
) estadoInicial