Las funciones puras son el tipo de funciones toman valores como argumentos, procesan algunos de esos valores y luego devuelven un valor de resultado. Una función pura no depende del "estado del mundo". El cálculo es totalmente autónomo e independiente. Dados los mismos argumentos, una función pura siempre devolverá el mismo resultado.
I/O es impuro. Las operaciones de entrada y salida son impuras. Influyen e interactúan con el "mundo exterior". Esencialmente, esta es la única forma de hacer que las computadoras hagan cosas interesantes.
La función getLine lee la entrada del usuario y la devuelve como un tipo especial de valor de cadena: una cadena de I/O. La función putStrLn toma una entrada de cadena y la imprime en el terminal, devolviendo un valor de IO vacío, es decir, IO ().
Los tipos IO es lo que nos permite no mezclar funciones puras e impuras: el sistema de tipos nos mantiene honestos. Sabemos por el tipo de función si está involucrado con I/O.
Veamos esta simple función :
let greet() = do
planet <- getLine
home <- getLine
putStrLn ("greetings " ++ planet ++ "ling.")
putStrLn ("I am from " ++ home ++ ".")
putStrLn "Take me to your leader."
Podemos escribirlo como una (¡muy larga!) Línea única:
do { planet <- getLine; home <- getLine; putStrLn ("greetings " ++ planet ++ "ling."); putStrLn ("I am from " ++ home ++ "."); putStrLn "Take me to your leader."}
Si ejecutamos esto, escribiendo "Earth" y "Mars". Entonces deberías ver:
greetings Earthling.
I am from Mars.
Take me to your leader.
Tenga en cuenta que el orden es importante aquí:
queremos que la primera llamada getLine obtenga el nombre del planeta en el que hemos aterrizado
queremos que la segunda llamada getLine obtenga el nombre de dónde somos.
El orden de evaluación de funciones no importa en código puro, por Ej.
let a = reverse "winston"
b = reverse "churchill"
in "sir " ++ a ++ " " ++ b
No importa si hacemos el primer reverso antes del segundo; el resultado de la expresión sigue siendo el mismo. Sin embargo, este no es el caso de I/O. La secuencia es vital para las acciones de I/O.
La notación do nos permite secuenciar acciones. Esto se parece a una secuencia de comandos en un lenguaje de programación imperativo. Sin embargo, hacer es solo azúcar sintáctico. Debajo, se reescribe como una cadena de llamadas de función donde la salida de la primera función se convierte en la entrada de la segunda función. El operador de enlace realiza esta secuencia de funciones. Es una parte clave de la mónada IO. Estamos comenzando a arañar la superficie de Haskell IO y descubrimos que es muy complejo. Reservaremos la discusión de Mónadas y operadores de enlace para más adelante en el curso.
Por ahora, todo lo que necesitamos entender es:
- Las operaciones de I/O son impuras
- Puede usar do para especificar una secuencia de acciones
- use <- dentro de un do para asociar valores de entrada con nombres
- cualquier valor o función que implique I/O tiene IO en su tipo
- una secuencia de acciones de I/O se describe como estar en la Mónada de IO