Suche…


Lesen des gesamten Inhalts der Standardeingabe in eine Zeichenfolge

main = do
    input <- getContents
    putStr input

Eingang:

This is an example sentence.
And this one is, too!

Ausgabe:

This is an example sentence.
And this one is, too!

Hinweis: Dieses Programm druckt tatsächlich Teile der Ausgabe, bevor alle Eingaben vollständig eingelesen sind. Wenn Sie beispielsweise getContents über eine 50-MB-Datei verwenden, stellen Haskells faule Auswertung und Garbage Collection sicher, dass nur die Teile der Datei, die aktuell benötigt werden (Lesen: für die weitere Ausführung unerläßlich), werden in den Speicher geladen. Daher wird die 50-MB-Datei nicht sofort in den Speicher geladen.

Eine Zeile aus der Standardeingabe lesen

main = do
    line <- getLine
    putStrLn line

Eingang:

This is an example.

Ausgabe:

This is an example.

Analysieren und Erstellen eines Objekts anhand der Standardeingabe

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 )

Eingang:

Type the first number: 9.5
Type the second number: -2.02

Ausgabe:

9.5 + -2.02 = 7.48

Lesen aus Dateihandles

Wie in einigen anderen Teilen der E / A-Bibliothek haben Funktionen, die implizit einen Standardstream verwenden, in System.IO ein Gegenstück, das denselben Job ausführt, jedoch mit einem zusätzlichen Parameter links vom Typ Handle , der den Stream darstellt abgewickelt. Zum Beispiel hat getLine :: IO String ein Gegenstück 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

Inhalt der Datei example.txt :

This is an example.
Hello, world!
This is another example.

Eingang:

Type a filename: example.txt

Ausgabe:

example.txt:1: This is an example.
example.txt:2: Hello, world!
example.txt:3: This is another example

Prüfen auf Bedingungen am Dateiende

Ein wenig kontrapunktisch zu der Vorgehensweise der Standard-E / A-Bibliotheken der meisten anderen Sprachen. Für Haskells isEOF müssen Sie keine Leseoperation ausführen, bevor Sie eine EOF-Bedingung prüfen. Die Laufzeit erledigt das für Sie.

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

Eingang:

Line #1.
Line #2.
Line #3.

Ausgabe:

End-of-file reached at line 4.

Wörter aus einer ganzen Datei lesen

In Haskell ist es oft sinnvoll, sich nicht mit Dateihandles zu beschäftigen, sondern einfach eine ganze Datei direkt von der Festplatte in den Speicher zu lesen oder zu schreiben und die gesamte Partitionierung / Verarbeitung des Textes mit der reinen String-Datenstruktur durchzuführen. Dies vermeidet das Mischen von IO und Programmlogik, was erheblich dazu beitragen kann, Fehler zu vermeiden.

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

Wenn loremipsum.txt enthält

Lorem ipsum dolor sit amet,
consectetur adipiscing elit

dann wird das Programm ausgegeben

elit
adipiscing
consectetur
amet,
sit
dolor
ipsum
Lorem

mapM_ ging hier die Liste aller Wörter in der Datei durch und druckte jedes einzelne mit putStrLn in eine eigene Zeile.


Wenn Sie denken, dass dies für das Gedächtnis verschwenderisch ist, haben Sie einen Punkt. Tatsächlich kann Haskells Faulheit oft verhindern, dass sich die gesamte Datei gleichzeitig im Speicher befinden muss ... aber Achtung, diese Art von Lazy IO verursacht ihre eigenen Probleme. Bei leistungskritischen Anwendungen ist es oft sinnvoll, die gesamte Datei auf einmal strikt zu lesen. Sie können dies mit der Data.Text Version von readFile .

IO definiert die Hauptaktion Ihres Programms

Um ein Haskell - Programm macht ausführbar Sie eine Datei mit einer bereitstellen muss main vom Typ IO ()

main :: IO ()
main = putStrLn "Hello world!"

Wenn Haskell kompiliert wird, werden hier die IO Daten untersucht und in eine ausführbare Datei umgewandelt. Wenn wir dieses Programm ausführen, wird Hello world! gedruckt Hello world! .

Wenn Sie Werte vom Typ IO a außer main sie nichts tun.

other :: IO ()
other = putStrLn "I won't get printed"

main :: IO ()
main = putStrLn "Hello world!"

Das Kompilieren und Ausführen dieses Programms hat den gleichen Effekt wie das letzte Beispiel. Der Code in other wird ignoriert.

Um den Code in machen other haben Runtime - Effekte , die Sie es in komponieren haben main . Keine IO Werte, die nicht eventuell in main werden, haben Auswirkungen auf die Laufzeit. So schreiben zwei IO - Werte sequenziell können Sie verwenden , 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

Wenn Sie dieses Programm kompilieren und ausführen, wird es ausgegeben

Hello world!
I will get printed... but only at the point where I'm composed into main

Beachten Sie, dass die Reihenfolge der Vorgänge dadurch beschrieben wird, wie other in main und nicht in Definitionsreihenfolge zusammengesetzt wurden.

Rolle und Zweck von IO

Haskell ist eine reine Sprache, was bedeutet, dass Ausdrücke keine Nebenwirkungen haben können. Ein Nebeneffekt ist alles, was der Ausdruck oder die Funktion tut, außer einen Wert zu erzeugen, z. B. einen globalen Zähler zu ändern oder an die Standardausgabe zu drucken.

In Haskell werden nebenwirkungsbereite Berechnungen (insbesondere solche, die Auswirkungen auf die reale Welt haben können) unter Verwendung von IO modelliert. Genau genommen ist IO ein Typkonstruktor, der einen Typ annimmt und einen Typ erzeugt. IO Int ist beispielsweise der Typ einer E / A-Berechnung, die einen Int Wert erzeugt. Der IO - Typ ist abstrakt, und die Schnittstelle zur Verfügung gestellt IO stellt sicher , dass bestimmte unzulässige Werte (das heißt, Funktionen mit nicht-sensical Typen) nicht existieren kann, indem sichergestellt wird, dass alle eingebauten Funktionen , die IO führen Sie einen Rückgabetyp in geschlossenen haben IO .

Wenn ein Haskell-Programm ausgeführt wird, wird die Berechnung ausgeführt, die durch den Haskell-Wert namens main , dessen Typ für jeden Typ x IO x kann.

IO-Werte bearbeiten

Die Standardbibliothek enthält viele Funktionen, die typische IO Aktionen bereitstellen, die eine Programmiersprache für allgemeine Zwecke ausführen soll, z. B. Lesen und Schreiben von Dateihandles. Allgemeine IO Aktionen werden erstellt und im Wesentlichen mit zwei Funktionen kombiniert:

 (>>=) :: IO a -> (a -> IO b) -> IO b

Diese Funktion (normalerweise als Bind bezeichnet ) führt eine IO Aktion und eine Funktion aus, die eine IO Aktion zurückgibt, und erzeugt die IO Aktion, die das Ergebnis der Anwendung der Funktion auf den durch die erste IO Aktion erzeugten Wert ist.

 return :: a -> IO a

Diese Funktion nimmt einen beliebigen Wert (dh einen reinen Wert) und gibt die E / A-Berechnung zurück, die keine E / A ausführt und den angegebenen Wert liefert. Mit anderen Worten, es handelt sich um eine No-Op-E / A-Aktion.

Es gibt zusätzliche allgemeine Funktionen, die häufig verwendet werden, aber alle können in Bezug auf die beiden obigen Funktionen geschrieben werden. Zum Beispiel ist (>>) :: IO a -> IO b -> IO b ähnlich (>>=) , das Ergebnis der ersten Aktion wird jedoch ignoriert.

Ein einfaches Programm, das den Benutzer mit folgenden Funktionen begrüßt:

 main :: IO ()
 main =
   putStrLn "What is your name?" >>
   getLine >>= \name ->
   putStrLn ("Hello " ++ name ++ "!")

Dieses Programm verwendet außerdem putStrLn :: String -> IO () und getLine :: IO String .


Hinweis: Die oben genannten Typen bestimmter Funktionen sind tatsächlich allgemeiner als die angegebenen Typen (nämlich >>= , >> und return ).

IO-Semantik

Der IO Typ in Haskell hat eine sehr ähnliche Semantik wie imperative Programmiersprachen. Zum Beispiel, wenn man s1 ; s2 schreibt s1 ; s2 in einer zwingenden Sprache, um die Ausführung von Anweisung s1 anzuzeigen, dann Anweisung s2 , kann man s1 >> s2 schreiben, um dasselbe in Haskell zu modellieren.

Die Semantik von IO weicht jedoch etwas von dem ab, was man aus einem imperativen Hintergrund erwarten würde. Die return Funktion unterbricht den Steuerungsfluss nicht - sie hat keine Auswirkungen auf das Programm, wenn eine andere IO Aktion nacheinander ausgeführt wird. Zum Beispiel gibt return () >> putStrLn "boom" korrekt auf die Standardausgabe.


Die formale Semantik von IO kann in Bezug auf einfache Gleichungen mit den Funktionen des vorherigen Abschnitts angegeben werden:

 return x >>= f ≡ f x, ∀ f x
 y >>= return ≡ return y, ∀ y
 (m >>= f) >>= g ≡ m >>= (\x -> (f x >>= g)), ∀ m f g

Diese Gesetze werden typischerweise als linke Identität, rechte Identität bzw. Zusammensetzung bezeichnet. Sie können natürlicher in Bezug auf die Funktion angegeben werden

 (>=>) :: (a -> IO b) -> (b -> IO c) -> a -> IO c
 (f >=> g) x = (f x) >>= g

wie folgt:

 return >=> f ≡ f, ∀ f
 f >=> return ≡ f, ∀ f
 (f >=> g) >=> h ≡ f >=> (g >=> h), ∀ f g h

Lazy IO

Funktionen, die E / A-Berechnungen ausführen, sind normalerweise streng, was bedeutet, dass alle vorhergehenden Aktionen in einer Folge von Aktionen abgeschlossen sein müssen, bevor die nächste Aktion gestartet wird. In der Regel ist dies ein nützliches und erwartetes Verhalten - putStrLn "X" >> putStrLn "Y" sollte "XY" drucken. Bestimmte Bibliotheksfunktionen führen jedoch eine langsame E / A aus, was bedeutet, dass die zur Erzeugung des Werts erforderlichen E / A-Aktionen nur ausgeführt werden, wenn der Wert tatsächlich verbraucht wird. Beispiele für solche Funktionen sind getContents und readFile . Lazy I / O kann die Leistung eines Haskell-Programms drastisch reduzieren. Wenn Sie also Bibliotheksfunktionen verwenden, ist zu beachten, welche Funktionen faul sind.

IO und do Notation

Haskell bietet eine einfachere Methode zum Kombinieren verschiedener E / A-Werte zu größeren E / A-Werten. Diese spezielle Syntax heißt do notation * und ist einfach syntaktischer Zucker für die Verwendung der Funktionen >>= , >> und return .

Das Programm im vorherigen Abschnitt kann auf zwei verschiedene Arten mit do Notation geschrieben werden, wobei die erste für das Layout und die zweite für das Layout unabhängig ist:

 main = do
   putStrLn "What is your name?"
   name <- getLine
   putStrLn ("Hello " ++ name ++ "!")


 main = do {
   putStrLn "What is your name?" ;
   name <- getLine ;
   putStrLn ("Hello " ++ name ++ "!")
   }

Alle drei Programme sind genau gleichwertig.


* Beachten Sie, dass do Notation auch für eine breitere Klasse von Typkonstruktoren ( Monaden) anwendbar ist.

'A' 'aus' 'IO a' holen

Eine häufig gestellte Frage lautet: "Ich habe einen Wert von IO a , aber ich möchte etwas dazu tun, a Wert: Wie erhalte ich Zugriff darauf?" Wie kann man mit Daten arbeiten, die von außen kommen (z. B. eine vom Benutzer eingegebene Zahl inkrementieren)?

Der Punkt ist, dass, wenn Sie eine reine Funktion für Daten verwenden, die als unrein erachtet wurden, das Ergebnis immer noch unrein ist. Das hängt davon ab, was der Benutzer getan hat! Ein Wert des Typs IO a steht für eine "Nebeneffektberechnung, die zu einem Wert des Typs a ", der nur ausgeführt werden kann, wenn (a) er zu main und (b) das Programm kompiliert und ausgeführt wird. Aus diesem Grund gibt es in der reinen Haskell-Welt keine Möglichkeit, "das a rauszuholen".

Stattdessen wollen wir eine neue Berechnung, den Bau eines neuen IO - Wert, der die Verwendung des macht a Wert zur Laufzeit. Dies ist eine weitere Möglichkeit , IO Werte zu erstellen. Daher können wir do -notation erneut verwenden:

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

Hier verwenden wir eine reine Funktion ( getMessage ) ein drehen Int in einen String , aber wir verwenden do Notation , um es auf das Ergebnis einer angelegten machen wird IO Berechnung myComputation wenn (nach) , dass die Berechnung ausgeführt wird . Das Ergebnis ist eine größere IO Berechnung, newComputation . Diese Technik der Verwendung reiner Funktionen in einem unreinen Kontext wird als Heben bezeichnet .

Schreiben an stdout

Gemäß der Haskell 2010-Sprachspezifikation sind die folgenden Standard-E / A-Funktionen in Prelude verfügbar, sodass für deren Verwendung keine Importe erforderlich sind.

putChar :: Char -> IO () - schreibt ein char in stdout

Prelude> putChar 'a'
aPrelude>  -- Note, no new line

putStr :: String -> IO () - schreibt einen String in stdout

Prelude> putStr "This is a string!"
This is a string!Prelude>  -- Note, no new line

putStrLn :: String -> IO () - schreibt einen String in stdout und fügt eine neue Zeile hinzu

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

Erinnern Sie sich daran, dass Sie Show für Ihre eigenen Typen instanziieren können, indem Sie deriving :

-- In ex.hs
data Person = Person { name :: String } deriving Show
main = print (Person "Alex")  -- Person is an instance of `Show`, thanks to `deriving`

Laden und Ausführen in 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>

Lesen von "stdin"

Gemäß der Haskell 2010-Sprachspezifikation sind die folgenden Standard-E / A-Funktionen in Prelude verfügbar, sodass für deren Verwendung kein Import erforderlich ist.

getChar :: IO Char - Lies ein Char von 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 - Liest einen String aus stdin , ohne neue Zeile

Prelude> getLine
Hello there!  -- user enters some text and presses enter
"Hello there!"

read :: Read a => String -> a - Konvertiert einen String in einen Wert

Prelude> read "1" :: Int
1
Prelude> read "1" :: Float
1.0
Prelude> read "True" :: Bool
True

Andere, weniger gebräuchliche Funktionen sind:

  • getContents :: IO String - gibt alle Benutzereingaben als eine einzige Zeichenfolge zurück, die nur nach Bedarf gelesen wird
  • interact :: (String -> String) -> IO () - nimmt eine Funktion des Typs String-> String als Argument an. Die gesamte Eingabe des Standard-Eingabegeräts wird als Argument an diese Funktion übergeben


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow