【问题标题】:Is there a way to unwrap a type from an IO monad?有没有办法从 IO monad 中解开类型?
【发布时间】:2014-02-27 08:43:15
【问题描述】:

我有这个非常简单的功能

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-notation,但我发现这样使用起来更清晰)

我的问题是;有没有办法让我放弃IO 部分,以便我可以单独使用字节串?

我知道您可以对某些 monad(例如 EitherMaybe)进行模式匹配以获取它们的值,但是您可以使用 IO 做类似的事情吗?

或者说不同的声音:有没有办法让readJFile 在没有 IO 的情况下返回 Maybe Response

【问题讨论】:

  • 我不是在谈论内置的东西,我只是在寻求一种方法来做到这一点,我也许可以构建自己;我的代码现在看起来的样子,我必须通过 5 个函数将那个 IO monad 拖到我身边,公平地说,它把它弄乱了很多,特别是考虑到我只在那个 ONE 函数中执行 IO 操作而没有其他地方
  • 你永远不需要通过任何函数“拖动一个 monad”,除非它们都需要实际执行 IO。只需使用fmap(或liftM / liftM2 / ...)将整个链提升到monad。
  • @ElectricCoffee 如果其他函数不做 IO,那么它们不应该在 IO 中。使它们依赖于字节串(即B.ByteString -> ... 类型),然后使用单个 IO 操作将读取与处理结合起来。
  • 没有。 readJFile 做 IO,所以它必须有一个 IO 类型。但是你可以把它抽象到字节串上,就像我上面说的那样。

标签: haskell io monads io-monad


【解决方案1】:

要扩展我的 cmets,您可以这样做:

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)

【讨论】:

  • 很干净,很简单,我喜欢!不幸的是,它根本不起作用
  • @ElectricCoffee 您发布的错误似乎与我的代码无关。它提到了一行Left err -&gt; return Nothing,它没有出现在我的代码中,然后是另一行Right ps -&gt; return Just ps,它也没有出现在我的代码中。
【解决方案2】:

您永远不需要通过任何函数“拖动 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 在没有IO 的情况下返回Maybe Response

,这不可能,因为readJFile 确实需要做 IO。那么,没有办法从IO monad 中逃脱,这就是它的全部意义所在! (嗯,正如 Ricardo 所说,有 unsafePerformIO,但这绝对不是一个有效的应用程序。)

如果是在 IO monad 中解包 Maybe 值以及其中带有括号的签名的笨拙,您可能需要查看 MaybeT transformer

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

【讨论】:

    【解决方案3】:

    不,没有从 IO monad 中获取值的安全方法。相反,您应该通过使用 fmap 或 bind (>>=) 应用函数来完成 IO monad 内部的工作。此外,当您希望结果为 Maybe 时,您应该使用 decode 而不是 eitherDecode。

    getJson :: IO B.ByteString
    getJson = B.readFile jsonFile
    
    parseResponse :: B.ByteString -> Maybe Response
    parseResponse = decode
    
    readJFile :: IO (Maybe Response)
    readJFile = fmap parseResponse getJSON
    

    如果你更清楚,你也可以使用 do 表示法:

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

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

    【讨论】:

      【解决方案4】:

      一般来说,是的,有办法。伴随着很多“但是”,但是有。你问的是所谓的不安全的 IO 操作System.IO.Unsafe。它通常用于在调用外部库时编写包装器,而不是在常规 Haskell 代码中使用。

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

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

      【讨论】:

      • 从不向初学者推荐unsafePerformIO。在某些情况下,即使只是提及它也似乎是一个合理的选择,但事实并非如此。初学者永远不需要使用或知道unsafePerformIO。我之所以提到这一点,是因为对于我和其他人来说,当我们学习 Haskell 时知道 unsafePerformIO 实际上让学习变得更加困难,因为它给人的印象是你应该在某个时候“突破” IO monad ,而你不是。
      • 您是否真的阅读了我的回答,先生,轻松否决?我没有推荐它,我只是陈述了一个事实。我的回答不是宣传它,也不是错误的。不喜欢就不要点赞。在某些情况下,当您必须与外部库交互(可能为它们编写包装器)时,需要不安全的操作。好吧,这不是初学者的东西,但只要它是正确的,我应该和不应该告诉人们什么由你决定。
      • 我还想指出,SO 旨在成为答案的存储库,这适用于正在搜索信息的人。尽管他是初学者并且不需要 unsafePerformIO,但提及它以供参考可能会对将来真正需要它的人有所帮助@kqr。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多