Sie können dies ohne irgendwelche Hacks tun.
Wenn Sie nur stdin
in einem String
lesen möchten, benötigen Sie keine der Funktionen unsafe*
.
IO
ist ein Monad, und ein Monad ist ein Applicative Functor. A Functor wird durch die Funktion fmap
, dessen Signatur definiert ist:
fmap :: Functor f => (a -> b) -> f a -> f b
, die diese beiden Gesetze erfüllt:
fmap id = id
fmap (f . g) = fmap f . fmap g
Effektiv fmap
wendet eine Funktion auf Werte gewickelt.
Gegeben ein bestimmtes Zeichen 'c'
, was ist der Typ fmap ('c':)
? Wir können die zwei Typen aufschreiben, dann sie vereinigen:
fmap :: Functor f => (a -> b ) -> f a -> f b
('c':) :: [Char] -> [Char]
fmap ('c':) :: Functor f => ([Char] -> [Char]) -> f [Char] -> f [Char]
Hinweis darauf, dass IO
ist ein Funktor, wenn wir myGetContents :: IO [Char]
definieren wollen, scheint es sinnvoll, diese zu verwenden:
myGetContents :: IO [Char]
myGetContents = do
x <- getChar
fmap (x:) myGetContents
Dies ist in der Nähe , aber nicht ganz äquivalent zu getContents
, da diese Version versuchen wird, über das Ende der Datei hinaus zu lesen und einen Fehler auszulösen, anstatt eine Zeichenfolge zurückzugeben.Wenn man es nur anschaut, sollte man das klarstellen: Es gibt keine Möglichkeit, eine konkrete Liste, nur eine unendliche Betrugskette, zurückzugeben. Zu wissen, dass der konkrete Fall ""
bei EOF ist (und mit der Infix Syntax <$>
für fmap
) bringt uns zu:
import System.IO
myGetContents :: IO [Char]
myGetContents = do
reachedEOF <- isEOF
if reachedEOF
then return []
else do
x <- getChar
(x:) <$> myGetContents
Die Applicative Klasse bietet eine (leichte) Vereinfachung.
Erinnern Sie sich daran, dass IO
ein Applicative Functor ist, nicht nur irgendein alter Functor. Es gibt „Applicative Laws“ im Zusammenhang mit dieser typeclass ähnlich wie die „Functor Gesetze“, aber wir werden speziell Blick auf <*>
:
<*> :: Applicative f => f (a -> b) -> f a -> f b
Dies ist fast identisch mit fmap
(aka <$>
), mit der Ausnahme, dass die Funktion zu bewerben ist auch verpackt.
import System.IO
myGetContents :: IO String
myGetContents = do
reachedEOF <- isEOF
if reachedEOF
then return []
else (:) <$> getChar <*> myGetContents
Eine Änderung ist notwendig, wenn der Eingang unendlich sein kann: Wir können dann mithilfe des Applicative Stils die bind in unserer else
Klausel vermeiden.
Denken Sie daran, wenn ich sagte, dass Sie nicht die unsafe*
Funktionen benötigen, wenn Sie nur alle von stdin
in eine String
lesen wollen? Nun, wenn Sie nur einige der Eingabe möchten, tun Sie. Wenn Ihre Eingabe unendlich lang sein könnte, tun Sie das auf jeden Fall. Das endgültige Programm unterscheidet sich in einem Ein- und einem einzigen Wort:
import System.IO
import System.IO.Unsafe
myGetContents :: IO [Char]
myGetContents = do
reachedEOF <- isEOF
if reachedEOF
then return []
else (:) <$> getChar <*> unsafeInterleaveIO myGetContents
Die definierende Funktion der lazy IO ist unsafeInterleaveIO
(von System.IO.Unsafe
). Dies verzögert die Berechnung der Aktion IO
, bis sie angefordert wird.
Funktioniert wie ein Charme :) Bleibt hinzuzufügen, dass 'unsafeInterleaveIO' von' System.IO.Unsafe' importiert werden muss. – johanneslink