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!
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


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow