Haskell Language
IO
Buscar..
Leyendo todos los contenidos de entrada estándar en una cadena
main = do
input <- getContents
putStr input
Entrada:
This is an example sentence.
And this one is, too!
Salida:
This is an example sentence.
And this one is, too!
Nota: este programa realmente imprimirá partes de la salida antes de que se haya leído completamente toda la entrada. Esto significa que, si, por ejemplo, usa getContents
sobre un archivo de 50MiB, la evaluación perezosa de Haskell y el recolector de basura se asegurarán de que solo el Las partes del archivo que se necesitan actualmente (lectura: indispensable para una ejecución posterior) se cargarán en la memoria. Por lo tanto, el archivo 50MiB no se cargará en la memoria de una vez.
Leyendo una línea de entrada estándar
main = do
line <- getLine
putStrLn line
Entrada:
This is an example.
Salida:
This is an example.
Analizar y construir un objeto desde la entrada estándar
readFloat :: IO Float
readFloat =
fmap read getLine
main :: IO ()
main = do
putStr "Type the first number: "
first <- readFloat
putStr "Type the second number: "
second <- readFloat
putStrLn $ show first ++ " + " ++ show second ++ " = " ++ show ( first + second )
Entrada:
Type the first number: 9.5
Type the second number: -2.02
Salida:
9.5 + -2.02 = 7.48
Leyendo desde los manejadores de archivos
Al igual que en otras partes de la biblioteca de E / S, las funciones que utilizan implícitamente un flujo estándar tienen una contraparte en System.IO
que realiza el mismo trabajo, pero con un parámetro adicional a la izquierda, de tipo Handle
, que representa el flujo que se está manejado. Por ejemplo, getLine :: IO String
tiene una contraparte hGetLine :: Handle -> IO String
.
import System.IO( Handle, FilePath, IOMode( ReadMode ),
openFile, hGetLine, hPutStr, hClose, hIsEOF, stderr )
import Control.Monad( when )
dumpFile :: Handle -> FilePath -> Integer -> IO ()
dumpFile handle filename lineNumber = do -- show file contents line by line
end <- hIsEOF handle
when ( not end ) $ do
line <- hGetLine handle
putStrLn $ filename ++ ":" ++ show lineNumber ++ ": " ++ line
dumpFile handle filename $ lineNumber + 1
main :: IO ()
main = do
hPutStr stderr "Type a filename: "
filename <- getLine
handle <- openFile filename ReadMode
dumpFile handle filename 1
hClose handle
Contenido del archivo example.txt
:
This is an example.
Hello, world!
This is another example.
Entrada:
Type a filename: example.txt
Salida:
example.txt:1: This is an example.
example.txt:2: Hello, world!
example.txt:3: This is another example
Comprobación de condiciones de fin de archivo
Un poco contrario a la intuición de la forma en que lo hacen las bibliotecas de E / S estándar de otros idiomas, el isEOF
de Haskell no requiere que realice una operación de lectura antes de verificar una condición de EOF; El tiempo de ejecución lo hará por ti.
import System.IO( isEOF )
eofTest :: Int -> IO ()
eofTest line = do
end <- isEOF
if end then
putStrLn $ "End-of-file reached at line " ++ show line ++ "."
else do
getLine
eofTest $ line + 1
main :: IO ()
main =
eofTest 1
Entrada:
Line #1.
Line #2.
Line #3.
Salida:
End-of-file reached at line 4.
Leyendo palabras de un archivo completo
En Haskell, a menudo tiene sentido no molestarse en manejar los archivos , sino simplemente leer o escribir un archivo completo directamente desde el disco a la memoria † , y hacer toda la partición / procesamiento del texto con la estructura de datos de cadena pura. Esto evita mezclar IO y la lógica del programa, lo que puede ayudar enormemente a evitar errores.
-- | The interesting part of the program, which actually processes data
-- but doesn't do any IO!
reverseWords :: String -> [String]
reverseWords = reverse . words
-- | A simple wrapper that only fetches the data from disk, lets
-- 'reverseWords' do its job, and puts the result to stdout.
main :: IO ()
main = do
content <- readFile "loremipsum.txt"
mapM_ putStrLn $ reverseWords content
Si loremipsum.txt
contiene
Lorem ipsum dolor sit amet,
consectetur adipiscing elit
entonces el programa dará salida
elit
adipiscing
consectetur
amet,
sit
dolor
ipsum
Lorem
Aquí, mapM_
la lista de todas las palabras en el archivo e imprimió cada una de ellas en una línea separada con putStrLn
.
† Si crees que esto es un desperdicio de memoria, tienes un punto. En realidad, la pereza de Haskell a menudo puede evitar que todo el archivo deba residir en la memoria simultáneamente ... pero tenga cuidado, este tipo de IO perezosa causa su propio conjunto de problemas. Para aplicaciones de rendimiento crítico, a menudo tiene sentido imponer el archivo completo para que se lea a la vez, estrictamente; se puede hacer esto con el Data.Text
versión de readFile
.
IO define la acción `main` de su programa.
Para hacer un programa ejecutable de Haskell, debe proporcionar un archivo con una función main
de tipo IO ()
main :: IO ()
main = putStrLn "Hello world!"
Cuando Haskell se compila, examina los datos de IO
aquí y los convierte en un ejecutable. Cuando ejecutemos este programa se imprimirá Hello world!
.
Si tiene valores de tipo IO a
no sean main
, no harán nada.
other :: IO ()
other = putStrLn "I won't get printed"
main :: IO ()
main = putStrLn "Hello world!"
Compilar este programa y ejecutarlo tendrá el mismo efecto que el último ejemplo. El código en other
se ignora.
Para hacer que el código en other
tenga efectos de tiempo de ejecución, debe componerlo en main
. Ningún valor IO
que no se componga finalmente en main
tendrá ningún efecto de tiempo de ejecución. Para componer dos valores de IO
secuencial, puede usar do
notation:
other :: IO ()
other = putStrLn "I will get printed... but only at the point where I'm composed into main"
main :: IO ()
main = do
putStrLn "Hello world!"
other
Cuando compilas y ejecutas este programa sale
Hello world!
I will get printed... but only at the point where I'm composed into main
Tenga en cuenta que el orden de las operaciones se describe según la composición de los other
en main
y no en el orden de definición.
Papel y propósito de IO
Haskell es un lenguaje puro, lo que significa que las expresiones no pueden tener efectos secundarios. Un efecto secundario es cualquier cosa que la expresión o función haga que no sea producir un valor, por ejemplo, modificar un contador global o imprimir en una salida estándar.
En Haskell, los cálculos de efectos secundarios (específicamente, aquellos que pueden tener un efecto en el mundo real) se modelan utilizando IO
. En sentido estricto, IO
es un constructor de tipos, que toma un tipo y produce un tipo. Por ejemplo, IO Int
es el tipo de cálculo de E / S que produce un valor Int
. El tipo de IO
es abstracto y la interfaz provista para IO
garantiza que no puedan existir ciertos valores ilegales (es decir, funciones con tipos no sensitivos), al garantizar que todas las funciones incorporadas que realizan IO tienen un tipo de retorno incluido en IO
.
Cuando se ejecuta un programa de Haskell, se ejecuta el cálculo representado por el valor de Haskell denominado main
, cuyo tipo puede ser IO x
para cualquier tipo x
.
Manipulando valores IO
Hay muchas funciones en la biblioteca estándar que proporcionan acciones típicas de IO
que debe realizar un lenguaje de programación de propósito general, como leer y escribir en los manejadores de archivos. Las acciones generales de IO
se crean y combinan principalmente con dos funciones:
(>>=) :: IO a -> (a -> IO b) -> IO b
Esta función (normalmente llamada vinculación ) toma una acción de IO
y una función que devuelve una acción de IO
, y produce la acción de IO
que es el resultado de aplicar la función al valor producido por la primera acción de IO
.
return :: a -> IO a
Esta función toma cualquier valor (es decir, un valor puro) y devuelve el cálculo de IO que no hace IO y produce el valor dado. En otras palabras, es una acción de E / S sin operación.
Hay funciones generales adicionales que se usan a menudo, pero todas pueden escribirse en los términos de los dos anteriores. Por ejemplo, (>>) :: IO a -> IO b -> IO b
es similar a (>>=)
pero se ignora el resultado de la primera acción.
Un programa simple que saluda al usuario usando estas funciones:
main :: IO ()
main =
putStrLn "What is your name?" >>
getLine >>= \name ->
putStrLn ("Hello " ++ name ++ "!")
Este programa también usa putStrLn :: String -> IO ()
y getLine :: IO String
.
Nota: los tipos de ciertas funciones anteriores son en realidad más generales que los tipos dados (a saber, >>=
, >>
y return
).
Semántica IO
El tipo IO
en Haskell tiene una semántica muy similar a la de los lenguajes de programación imperativos. Por ejemplo, cuando uno escribe s1 ; s2
en un lenguaje imperativo para indicar la ejecución de la instrucción s1
, luego la instrucción s2
, se puede escribir s1 >> s2
para modelar lo mismo en Haskell.
Sin embargo, la semántica de IO
divergente ligeramente de lo que se esperaría que provenga de un fondo imperativo. La función de return
no interrumpe el flujo de control; no tiene ningún efecto en el programa si se ejecuta otra acción de IO
en secuencia. Por ejemplo, return () >> putStrLn "boom"
imprime correctamente "boom" en la salida estándar.
La semántica formal de IO
puede dar en términos de ecualizaciones simples que involucran las funciones en la sección anterior:
return x >>= f ≡ f x, ∀ f x
y >>= return ≡ return y, ∀ y
(m >>= f) >>= g ≡ m >>= (\x -> (f x >>= g)), ∀ m f g
Estas leyes suelen denominarse identidad de izquierda, identidad de derecha y composición, respectivamente. Se pueden afirmar más naturalmente en términos de la función.
(>=>) :: (a -> IO b) -> (b -> IO c) -> a -> IO c
(f >=> g) x = (f x) >>= g
como sigue:
return >=> f ≡ f, ∀ f
f >=> return ≡ f, ∀ f
(f >=> g) >=> h ≡ f >=> (g >=> h), ∀ f g h
Perezoso IO
Las funciones que realizan cálculos de E / S suelen ser estrictas, lo que significa que todas las acciones anteriores en una secuencia de acciones deben completarse antes de que comience la siguiente acción. Normalmente, este es un comportamiento útil y esperado - putStrLn "X" >> putStrLn "Y"
debe imprimir "XY". Sin embargo, ciertas funciones de la biblioteca realizan la E / S perezosamente, lo que significa que las acciones de E / S requeridas para producir el valor solo se realizan cuando el valor se consume realmente. Ejemplos de tales funciones son getContents
y readFile
. La perezosa de E / S puede reducir drásticamente el rendimiento de un programa Haskell, por lo que al usar las funciones de la biblioteca, se debe tener cuidado de observar qué funciones son perezosas.
IO y do
la notación
Haskell proporciona un método más simple para combinar diferentes valores de IO en valores de IO más grandes. Esta sintaxis especial se conoce como do
la notación * y es simplemente azúcar sintáctica para los usos de la >>=
, >>
y return
funciones.
El programa en la sección anterior se puede escribir de dos maneras diferentes usando la notación do
, la primera es sensible al diseño y la segunda no sensible al diseño:
main = do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Hello " ++ name ++ "!")
main = do {
putStrLn "What is your name?" ;
name <- getLine ;
putStrLn ("Hello " ++ name ++ "!")
}
Los tres programas son exactamente equivalentes.
* Tenga en cuenta que la notación do
también es aplicable a una clase más amplia de constructores de tipo llamados mónadas .
Obtener la 'a' "de" 'IO a'
Una pregunta común es "tengo un valor de IO a
, pero quiero hacer algo para que a
valor:? ¿Cómo puedo obtener acceso a ella" ¿Cómo se puede operar con datos que provienen del mundo exterior (por ejemplo, incrementando un número escrito por el usuario)?
El punto es que si utiliza una función pura en los datos obtenidos de forma impura, el resultado sigue siendo impuro. ¡Depende de lo que hizo el usuario! Un valor de tipo IO a
significa un "cálculo de efectos secundarios que resulta en un valor de tipo a
" que solo puede ejecutarse (a) componiéndolo en main
y (b) compilando y ejecutando su programa. Por esa razón, no hay manera dentro del mundo Haskell puro para "conseguir el a
cabo".
En su lugar, queremos construir un nuevo cálculo, un nuevo IO
valor, que hace uso de la a
valor en tiempo de ejecución. Esta es otra forma de componer valores de IO
y, de nuevo, podemos usar do
notation:
-- assuming
myComputation :: IO Int
getMessage :: Int -> String
getMessage int = "My computation resulted in: " ++ show int
newComputation :: IO ()
newComputation = do
int <- myComputation -- we "bind" the result of myComputation to a name, 'int'
putStrLn $ getMessage int -- 'int' holds a value of type Int
Aquí estamos usando una función pura ( getMessage
) para convertir un Int
en un String
, pero estamos usando la notación do
para hacer que se aplique al resultado de un cálculo de IO
myComputation
cuando (después) se ejecuta ese cálculo. El resultado es un cálculo de IO
más grande, newComputation
. Esta técnica de usar funciones puras en un contexto impuro se llama levantamiento .
Escribiendo al stdout
Según la especificación de idioma de Haskell 2010, las siguientes son funciones de IO estándar disponibles en Prelude, por lo que no se requieren importaciones para usarlas.
putChar :: Char -> IO ()
- escribe un char
en stdout
Prelude> putChar 'a'
aPrelude> -- Note, no new line
putStr :: String -> IO ()
- escribe un String
en stdout
Prelude> putStr "This is a string!"
This is a string!Prelude> -- Note, no new line
putStrLn :: String -> IO ()
- escribe un String
en stdout
y agrega una nueva línea
Prelude> putStrLn "Hi there, this is another String!"
Hi there, this is another String!
print :: Show a => a -> IO ()
- escribe a
instancia de Show
a stdout
Prelude> print "hi"
"hi"
Prelude> print 1
1
Prelude> print 'a'
'a'
Prelude> print (Just 'a') -- Maybe is an instance of the `Show` type class
Just 'a'
Prelude> print Nothing
Nothing
Recuerde que puede crear una instancia de Show
para sus propios tipos utilizando la deriving
:
-- In ex.hs
data Person = Person { name :: String } deriving Show
main = print (Person "Alex") -- Person is an instance of `Show`, thanks to `deriving`
Cargando y corriendo en GHCi:
Prelude> :load ex.hs
[1 of 1] Compiling ex ( ex.hs, interpreted )
Ok, modules loaded: ex.
*Main> main -- from ex.hs
Person {name = "Alex"}
*Main>
Leyendo de `stdin`
Según la especificación de lenguaje de Haskell 2010 , las siguientes son funciones de IO estándar disponibles en Prelude, por lo que no se requieren importaciones para usarlas.
getChar :: IO Char
- lee un Char
de stdin
-- MyChar.hs
main = do
myChar <- getChar
print myChar
-- In your shell
runhaskell MyChar.hs
a -- you enter a and press enter
'a' -- the program prints 'a'
getLine :: IO String
- lee un String
desde stdin
, sin una nueva línea de caracteres
Prelude> getLine
Hello there! -- user enters some text and presses enter
"Hello there!"
read :: Read a => String -> a
- convierte un String a un valor
Prelude> read "1" :: Int
1
Prelude> read "1" :: Float
1.0
Prelude> read "True" :: Bool
True
Otras funciones menos comunes son:
-
getContents :: IO String
- devuelve todas las entradas del usuario como una sola cadena, que se lee con pereza a medida que se necesita -
interact :: (String -> String) -> IO ()
- toma una función de tipo String -> String como su argumento. La entrada completa del dispositivo de entrada estándar se pasa a esta función como su argumento