Gibt es eine Möglichkeit, einen Typ aus einer IO-Monade auszupacken?
Frage
Ich habe diese sehr einfache Funktion
import qualified Data.ByteString.Lazy as B
getJson :: IO B.ByteString
getJson = B.readFile jsonFile
readJFile :: IO (Maybe Response)
readJFile = parsing >>= (\d ->
case d of
Left err -> return Nothing
Right ps -> return (Just ps))
where parsing = fmap eitherDecode getJson :: IO (Either String Response)
wo jsonFile
ist ein Pfad zu einer Datei auf meiner Festplatte (verzeihen Sie das Fehlen der Do-Notation, aber ich fand es klarer damit zu arbeiten)
meine Frage ist;gibt es eine Möglichkeit für mich, die IO
teil, damit ich alleine mit dem Bytestring arbeiten kann?
Ich weiß, dass Sie auf bestimmten Monaden wie Muster übereinstimmen können Either
und Maybe
um ihre Werte herauszuholen, aber kannst du etwas Ähnliches damit machen IO
?
Oder anders geäußert:gibt es einen Weg für mich zu machen readJFile
zurückgeben Maybe Response
ohne das IO?
Lösung
Um meine Kommentare zu erweitern, erfahren Sie hier, wie Sie es tun können:
getJson :: IO B.ByteString
getJson = B.readFile jsonFile -- as before
readJFile :: B.ByteString -> Maybe Response -- look, no IO
readJFile b = case eitherDecode b of
Left err -> Nothing
Right ps -> Just ps
Am Ende kombinierst du wieder alles in einer IO-Aktion:
getAndProcess :: IO (Maybe Response)
getAndProcess = do
b <- getJson
return (readJFile b)
Andere Tipps
Sie müssen niemals eine Monade durch irgendwelche Funktionen "ziehen", es sei denn, sie müssen alle tatsächlich IO ausführen.Heben Sie einfach die gesamte Kette mit in die Monade fmap
(oder liftM
/ liftM2
/ ...).
Beispielsweise,
f1 :: B.ByteString -> K
f2 :: K -> I
f3 :: K -> J
f4 :: I -> J -> M
und dein ganzes Ding soll so sein
m :: M
m = let k = "f1 getJson"
in f4 (f2 k) (f3 k)
Das können Sie einfach tun
m = fmap (\b -> let k = f1 b
in f4 (f2 k) (f3 k) )
getJson
Das könnte übrigens schöner aussehen mit do
Schreibweise:
m = do
b <- getJson
return $ let k = f1 b
in f4 (f2 k) (f3 k)
Bezüglich deiner Bearbeitung und der Frage
gibt es einen Weg für mich zu machen
readJFile
zurückgebenMaybe Response
ohne dieIO
?
Nein, das kann unmöglich funktionieren, weil readJFile
muss IO machen.Es gibt keinen Weg, dem zu entkommen IO
monade dann, das ist der springende Punkt!(Nun, es gibt unsafePerformIO
wie Ricardo sagt, aber das ist definitiv keine gültige Bewerbung dafür.)
Wenn es die Klobigkeit des Auspackens ist Maybe
werte in der IO
monade und die Signaturen mit Parens in ihnen, vielleicht möchten Sie sich das ansehen MaybeT
Transformator.
readJFile' :: MaybeT IO Response
readJFile' = do
b <- liftIO getJson
case eitherDecode b of
Left err -> mzero
Right ps -> return ps
Im Allgemeinen, ja, es gibt einen Weg.Begleitet von viel "aber", aber es gibt.Du fragst nach dem, was es heißt unsicherer IO-Betrieb: System.IO.Unsicher.Es wird normalerweise zum Schreiben von Wrappern beim Aufrufen externer Bibliotheken verwendet.Im regulären Haskell-Code kann darauf nicht zurückgegriffen werden.
Grundsätzlich können Sie anrufen unsafePerformIO :: IO a -> a
was genau das macht, was Sie wollen, es streift die IO
teil und gibt Ihnen den eingewickelten Wert des Typs zurück a
.Wenn Sie sich jedoch die Dokumentation ansehen, gibt es eine Reihe von Anforderungen, die Sie selbst an das System stellen sollten, die alle auf die gleiche Idee führen:auch wenn Sie die Operation über E / A ausgeführt haben, sollte die Antwort das Ergebnis einer Funktion sein, wie von jeder anderen Haskell-Funktion erwartet, die nicht funktioniert IO
:es sollte immer das gleiche Ergebnis ohne Nebenwirkungen haben, nur basierend auf den Eingabewerten.
Hier, gegeben Ihren Code, dies ist offensichtlich nicht der Fall, da Sie aus einer Datei lesen.Sie sollten einfach innerhalb der IO-Monade weiterarbeiten, indem Sie Ihre anrufen readJFile
aus einer anderen Funktion mit Ergebnistyp IO something
.Dann können Sie den Wert innerhalb des lesen IO
wrapper (in sein IO
selbst), arbeiten Sie daran und verpacken Sie das Ergebnis dann erneut in ein anderes IO
bei der Rückkehr.
Nein, es gibt keinen sicheren Weg, um einen Wert aus der IO-Monade herauszuholen.Stattdessen sollten Sie die Arbeit innerhalb der E / A-Monade erledigen, indem Sie Funktionen mit fmap oder bind (>>=) anwenden.Außerdem sollten Sie decode anstelle von eitherDecode wenn Sie möchten, dass Ihr Ergebnis in Maybe .
getJson :: IO B.ByteString
getJson = B.readFile jsonFile
parseResponse :: B.ByteString -> Maybe Response
parseResponse = decode
readJFile :: IO (Maybe Response)
readJFile = fmap parseResponse getJSON
Sie könnten auch die do Notation verwenden, wenn Ihnen das klarer ist:
readJFile :: IO (Maybe Response)
readJFile = do
bytestring <- getJson
return $ decode bytestring
Beachten Sie, dass Sie die parseResponse Funktion nicht einmal benötigen, da readJFile den Typ angibt.