有没有办法从IO monad打开一个类型?

时间:2014-02-27 08:43:15

标签: haskell io monads 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是我硬盘上文件的路径(原谅缺少写法,但我发现这更清楚了)

我的问题是;有没有办法抛弃IO部分,这样我就可以单独处理字节串了?

我知道你可以在EitherMaybe之类的某些monad上进行模式匹配以获取其值,但是你可以使用IO做类似的事情吗?

或者表达方式不同:有没有办法让readJFile在没有IO的情况下返回Maybe Response

4 个答案:

答案 0 :(得分:4)

你永远不需要通过任何函数“拖动monad”,除非他们都需要实际执行IO。只需使用fmap(或liftM / liftM2 / ...)将整个链条提升到monad中。

例如,

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 monad中解包IO值以及包含parens的签名是笨拙的话,您可能需要查看MaybeT transformer

readJFile' :: MaybeT IO Response
readJFile' = do
   b <- liftIO getJson
   case eitherDecode b of
     Left err -> mzero
     Right ps -> return ps

答案 1 :(得分:4)

要扩展我的评论,请按以下步骤操作:

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

最后,您再次将所有内容合并到一个IO操作中:

getAndProcess :: IO (Maybe Response)
getAndProcess = do
  b <- getJson
  return (readJFile b)

答案 2 :(得分:3)

总的来说,是的,有办法。伴随着很多“但是”,但也有。您要求的是什么叫做不安全的IO操作System.IO.Unsafe。它通常用于在调用外部库时编写包装器,而不是在常规Haskell代码中使用它。

基本上,您可以调用unsafePerformIO :: IO a -> a来完成您想要的操作,它会删除IO部分,并为您提供类型为a的包装值。但是,如果你看一下文档,你应该向系统保证一些要求,这些要求都是相同的想法:即使你通过IO执行操作,答案也应该是函数,正如任何其他haskell函数所期望的那样,它不在IO中运行:它应该始终具有相同的结果而没有副作用,仅基于输入值。

在这里,鉴于您的代码,显然不是这种情况,因为您正在从文件中读取。您应该继续在IO monad中工作,方法是在结果类型为readJFile的另一个函数中调用IO something。然后,您将能够读取IO包装器中的值(自己在IO中),对其进行处理,然后在返回时将结果重新包装到另一个IO

答案 3 :(得分:2)

不,没有安全的方法可以从IO monad中获取值。相反,你应该通过应用fmap或bind(&gt;&gt; =)函数来完成IO monad中的工作。当你希望你的结果在Maybe中时,你应该使用decode而不是任何Decode。

getJson :: IO B.ByteString
getJson = B.readFile jsonFile

parseResponse :: B.ByteString -> Maybe Response
parseResponse = decode

readJFile :: IO (Maybe Response)
readJFile = fmap parseResponse getJSON

如果您更清楚,也可以使用do notation:

readJFile :: IO (Maybe Response)
readJFile = do
    bytestring <- getJson
    return $ decode bytestring

请注意,您甚至不需要parseResponse函数,因为readJFile指定了类型。