Existe-t-il un moyen de déballer un type d'une monade IO ?
Question
J'ai cette fonction très simple
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)
où jsonFile
est un chemin vers un fichier sur mon disque dur (pardonnez le manque de notation do, mais j'ai trouvé cela plus clair à utiliser)
Ma question est;y a-t-il un moyen pour moi d'abandonner le IO
partie pour que je puisse travailler avec la chaîne d'octets seule ?
Je sais que vous pouvez faire correspondre des modèles sur certaines monades comme Either
et Maybe
pour faire ressortir leurs valeurs, mais pouvez-vous faire quelque chose de similaire avec IO
?
Ou exprimé différemment :y a-t-il un moyen pour moi de faire readJFile
retour Maybe Response
sans l'IO ?
La solution
Pour développer mes commentaires, voici comment procéder :
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
En fin de compte, vous combinez à nouveau tout en une seule action IO :
getAndProcess :: IO (Maybe Response)
getAndProcess = do
b <- getJson
return (readJFile b)
Autres conseils
Vous n'avez jamais besoin de "faire glisser une monade" à travers des fonctions, à moins qu'elles n'aient toutes besoin de réellement effectuer des E/S.Soulevez simplement toute la chaîne dans la monade avec fmap
(ou liftM
/ liftM2
/ ...).
Par exemple,
f1 :: B.ByteString -> K
f2 :: K -> I
f3 :: K -> J
f4 :: I -> J -> M
et tout ton truc est censé être comme
m :: M
m = let k = "f1 getJson"
in f4 (f2 k) (f3 k)
Vous pouvez simplement le faire
m = fmap (\b -> let k = f1 b
in f4 (f2 k) (f3 k) )
getJson
À propos, cela pourrait paraître plus joli avec do
notation:
m = do
b <- getJson
return $ let k = f1 b
in f4 (f2 k) (f3 k)
Concernant votre modification et la question
y a-t-il un moyen pour moi de faire
readJFile
retourMaybe Response
sans leIO
?
Non, ça ne peut pas marcher, parce que readJFile
doit faire IO.Il n'y a aucun moyen d'échapper au IO
monade alors, c'est tout l'intérêt !(Eh bien, il y a unsafePerformIO
comme le dit Ricardo, mais ce n'est certainement pas une application valable.)
Si c'est la maladresse du déballage Maybe
valeurs dans le IO
monade, et les signatures avec des parenthèses, vous voudrez peut-être regarder la MaybeT
transformateur.
readJFile' :: MaybeT IO Response
readJFile' = do
b <- liftIO getJson
case eitherDecode b of
Left err -> mzero
Right ps -> return ps
En général, oui, il existe un moyen.Accompagné de beaucoup de « mais », mais il y en a.Vous demandez ce qu'on appelle un opération d'E/S dangereuse: System.IO.Unsafe.Il est généralement utilisé pour écrire des wrappers lors de l'appel à des bibliothèques externes, ce n'est pas quelque chose auquel recourir dans le code Haskell standard.
En gros, vous pouvez appeler unsafePerformIO :: IO a -> a
qui fait exactement ce que vous voulez, il supprime le IO
part et vous redonne la valeur enveloppée de type a
.Mais si vous regardez la documentation, vous devez vous assurer d'un certain nombre d'exigences envers le système, qui aboutissent toutes à la même idée :même si vous avez effectué l'opération via IO, la réponse doit être le résultat d'une fonction, comme prévu de toute autre fonction Haskell qui ne fonctionne pas dans IO
:il devrait toujours avoir le même résultat sans effets secondaires, uniquement basé sur les valeurs d'entrée.
Ici, étant donné votre code, ce n'est évidemment PAS le cas, puisque vous lisez un fichier.Vous devez simplement continuer à travailler au sein de la monade IO, en appelant votre readJFile
depuis une autre fonction avec le type de résultat IO something
.Ensuite, vous pourrez lire la valeur dans le IO
emballage (étant dans IO
vous-même), travaillez dessus, puis réemballer le résultat dans un autre IO
au retour.
Non, il n'existe aucun moyen sûr d'obtenir une valeur de la monade IO.Au lieu de cela, vous devez effectuer le travail à l'intérieur de la monade IO en appliquant des fonctions avec fmap ou bind (>>=).Vous devez également utiliser decode au lieu de l'un ou l'autreDecode lorsque vous souhaitez que votre résultat soit dans Maybe.
getJson :: IO B.ByteString
getJson = B.readFile jsonFile
parseResponse :: B.ByteString -> Maybe Response
parseResponse = decode
readJFile :: IO (Maybe Response)
readJFile = fmap parseResponse getJSON
Vous pouvez également utiliser la notation do si cela est plus clair pour vous :
readJFile :: IO (Maybe Response)
readJFile = do
bytestring <- getJson
return $ decode bytestring
Notez que vous n'avez même pas besoin de la fonction parseResponse puisque readJFile spécifie le type.