有没有办法从 IO monad 中解开类型?
题
我有这个非常简单的功能
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)
在哪里 jsonFile
是我的硬盘驱动器上文件的路径(请原谅缺少 do 符号,但我发现这更容易使用)
我的问题是;有没有办法让我放弃 IO
部分以便我可以单独使用字节串?
我知道你可以对某些单子进行模式匹配,例如 Either
和 Maybe
来了解他们的价值观,但是你可以做类似的事情吗 IO
?
或者用不同的方式表达:我有办法做吗 readJFile
返回 Maybe Response
没有IO?
解决方案
在我的评论中扩展,这是你可以做到的:
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
.
最后,您将再次将所有内容组合在一起:
getAndProcess :: IO (Maybe Response)
getAndProcess = do
b <- getJson
return (readJFile b)
. 其他提示
您永远不需要通过任何函数“拖动 monad”,除非它们都需要实际执行 IO。只需将整个链提升到 monad 中即可 fmap
(或者 liftM
/ liftM2
/ ...).
例如,
f1 :: B.ByteString -> K
f2 :: K -> I
f3 :: K -> J
f4 :: I -> J -> M
你的整个事情应该是这样的
m :: M
m = let k = "f1 getJson"
in f4 (f2 k) (f3 k)
你可以简单地做
m = fmap (\b -> let k = f1 b
in f4 (f2 k) (f3 k) )
getJson
顺便说一句,这可能看起来更好 do
符号:
m = do
b <- getJson
return $ let k = f1 b
in f4 (f2 k) (f3 k)
关于您的编辑和问题
我有办法做吗
readJFile
返回Maybe Response
没有IO
?
不, ,这不可能行得通,因为 readJFile
确实需要做IO。没有办法逃脱 IO
monad 那么,这就是它的重点!(嗯,有 unsafePerformIO
正如里卡多所说,但这绝对不是一个有效的应用程序。)
如果是因为拆包的笨拙 Maybe
中的值 IO
monad 以及其中带有括号的签名,您可能需要查看 MaybeT
变压器.
readJFile' :: MaybeT IO Response
readJFile' = do
b <- liftIO getJson
case eitherDecode b of
Left err -> mzero
Right ps -> return ps
总的来说,是的,有办法。伴随着很多“但是”,但是有。你问的是所谓的 不安全的IO操作: 系统.IO.不安全. 。它通常用于在调用外部库时编写包装器,在常规 Haskell 代码中不应该使用它。
基本上你可以打电话 unsafePerformIO :: IO a -> a
这正是你想要的,它去掉了 IO
部分并返回类型的包装值 a
. 。但是,如果您查看文档,您应该向系统保证一些要求,这些要求最终都具有相同的想法:即使您通过 IO 执行操作,答案也应该是函数的结果,正如任何其他不在其中运行的 haskell 函数所预期的那样 IO
:仅基于输入值,它应该始终具有相同的结果,没有副作用。
在这里,给出您的代码, 显然情况并非如此, ,因为您正在读取文件。你应该继续在 IO monad 中工作,通过调用你的 readJFile
来自另一个具有结果类型的函数 IO something
. 。然后,您将能够读取其中的值 IO
包装器(在 IO
你自己),对其进行处理,然后将结果重新包装在另一个中 IO
回来时。
No, there is no safe way to get a value out of the IO monad. Instead you should do the work inside the IO monad by applying functions with fmap or bind (>>=). Also you should use decode instead of eitherDecode when you want your result to be 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
You could also use do notation if that is clearer to you:
readJFile :: IO (Maybe Response)
readJFile = do
bytestring <- getJson
return $ decode bytestring
Note that you dont even need the parseResponse function since readJFile specifies the type.