【问题标题】:Alternative IO error for <|><|> 的替代 IO 错误
【发布时间】:2015-07-07 07:04:32
【问题描述】:

我正在使用运算符&lt;|&gt;

import qualified Data.ByteString.Lazy as B
import Network.HTTP.Conduit (simpleHttp)
import Data.Aeson
import Data.Maybe

data FooBar = FooBar {
    name :: !Text,
    surname :: !Text
    } deriving (Show,Generic)

instance FromJSON FooBar
instance ToJSON FooBar

getFeed :: String -> String -> IO (FooBar)
getFeed foo bar =  decode <$> (B.readFile foo <|> simpleHttp bar)

但是当我尝试编译它时,我得到:

No instance for (Alternative IO) arising from a use of ‘<|>’
    In the second argument of ‘(<$>)’, namely
      ‘(B.readFile foo <|> simpleHttp bar)’
    In the expression:
      decode <$> (B.readFile foo <|> simpleHttp bar)
    In an equation for ‘getFeed’:
        getFeed env id
          = decode <$> (B.readFile foo <|> simpleHttp bar)

这个错误对我来说有点模糊。知道如何解决吗? (顺便说一句,从这个回复中得到一些见解:Confused by the meaning of the 'Alternative' type class and its relationship to other type classes

【问题讨论】:

  • 你想用 &lt;|&gt; 达到什么目的?
  • 如果第一个“readFile”由于某种原因失败(如文件未找到)移动到第二个,基本上是“如果存在则...否则...”的通用替代方案。尽管它们看起来具有相同的输出(字节字符串),但我认为它们也需要以某种方式“继承”替代 =>。

标签: haskell io-monad alternative-functor


【解决方案1】:

除了您的实际问题之外,这就是为什么 IO 不能成为替代方案的原因。

首先,你会为empty 做什么?它需要有 forall a. IO a 类型 - 一个返回任何类型值的 IO 操作!

第二,你如何定义失败(用于&lt;|&gt;)?对于Maybe 等类型,空很明显(Nothing),也可以将失败定义为Nothing。因此,如果第一个参数是 Nothing,则返回第二个。但是 IO 呢? monad 上失败的(令人困惑的)概念很神奇,并且没有在类型级别表示,换句话说,没有明确的失败值。

如果您将所有 IO 操作包装在 Maybe(或 Either)中,例如 readFile' :: FilePath -&gt; IO (Maybe String),它可以工作。然后通过解除&lt;|&gt; 你可以组合动作。您必须捕获失败(异常)并将它们转换为包装器中的Nothing

(为方便起见,可以为所有Alternative f, Monad m =&gt; m f 创建一个Alternative 实例,但这需要类型组合?我不确定)

【讨论】:

  • 谢谢。尝试应用到 monad IO 上下文听起来并不好,除非我首先执行每一个可能的操作并在之后组合结果(管理不确定性的奇怪方法)
  • 您的问题听起来不一定无法回答。 empty 可以抛出异常,失败可以定义为“抛出异常”。也许现代异常机制有点复杂,但“抛出这五种异常中的一种”可能也是一个很好的答案。我相信IO 甚至还有一个MonadPlus 实例——它的界面与Alternative 几乎相同。
  • (快速更新以保持诚实:我一直查看base 一直到版本3,并没有找到IOMonadPlus 实例。所以那部分我之前的评论是错误的。)
  • 根据 Hackage 的说法,instance Alternative IObase 版本 4.9.0.0 以来一直存在。似乎empty 只是用消息"mzero" 引发了一个错误。但是&lt;|&gt; 是做什么的呢?
  • @DannyuNDos - 根据消息来源,a &lt;|&gt; b 将运行a,如果它抛出异常,吞下异常并运行bhackage.haskell.org/package/base-4.10.1.0/docs/src/…
【解决方案2】:

这是一个基于博客帖子Playing Catch: Handling IO Exceptions with ErrorT 的示例。

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Lib where

import qualified Data.ByteString.Lazy as B
import Network.HTTP.Conduit (simpleHttp)
import Control.Monad.Base
import Control.Applicative
import Control.Monad.Error
import System.IO.Error

newtype MyApp a = MyApp {
  getApp :: ErrorT String IO a
  } deriving (Functor, Applicative, Alternative, Monad, MonadIO, MonadError String, MonadBase IO)

myReadFile path = do
  r <- liftIO $ tryIOError $ B.readFile path 
  case r of
    Left e  -> throwError (strMsg "readFile error")
    Right x -> return x

mySimpleHttp bar = do
  r <- liftIO $ tryIOError $ simpleHttp bar
  case r of
    Left e -> throwError (strMsg "simpleHttp error")
    Right x -> return x

getFeed foo bar =  myReadFile foo <|> mySimpleHttp bar

runApp = runErrorT . getApp

doit = do result <- runApp $ getFeed "some/file.txt" "http://example.com/"
          case result of
            Left e  -> putStrLn $ "error: " ++ e
            Right r -> do putStrLn $ "got a result"; print r

我在这个例子中非常明确 - 文章提到了可以减少样板代码量的方法。

我的阴谋集团build-depends: 设置:

build-depends: base >= 4.7 && < 5, bytestring, mtl, http-conduit, transformers-base

【讨论】:

  • 谢谢!非常有趣的建议,我也喜欢链接的文章。它可以轻松“集中”在堆栈底部运行 IO 的异常。
【解决方案3】:

错误只是说IO 不是Alternative 的实例,因此没有为它定义&lt;|&gt;,正如您从documentation 中看到的那样。您的意思是“尝试B.readFile foo,如果失败,请改用simpleHttp bar”?如果是这样,你可以这样做

catch (B.readFile foo) (\(_ :: SomeException) -> simpleHttp bar)

(catch 来自Control.Exception,您应该使用Data.ByteString.Strict 而不是Lazy 以确保在catch 的范围内引发异常;您还需要ScopedTypeVariables 扩展来编写上面而不是\e -&gt; let _ = e :: SomeException in simpleHttp bar)。

【讨论】:

  • 感谢您的提示。我试图通过异常来避免程序流,因为我想要一个“ 链”(或类似的)。无论如何,正如您所说,这可能是“类型”的问题。
猜你喜欢
  • 2018-07-30
  • 2013-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-18
  • 2013-06-05
  • 1970-01-01
  • 2018-03-19
相关资源
最近更新 更多