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