Haskell Language
IO
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!
print :: Show a => a -> IO ()
- schreibt a
Instanz von Show
in 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
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