<|> 的替代 IO 错误

Alternative IO error for <|>

我正在使用运算符 <|> 来:

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

错误只是说 IO 不是 Alternative 的实例,因此没有为它定义 <|>,正如您从 documentation 中看到的那样。你的意思是 "try B.readFile foo and if it fails, use simpleHttp bar instead" 吗?如果是这样,你可以做

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

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

除了你的实际问题,这就是 IO 不能替代的原因。

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

其次,你如何定义失败(用于<|>)?对于 Maybe 等类型,空是显而易见的(Nothing),失败也可以定义为 Nothing。因此,如果第一个参数为 Nothing,则 return 第二个。但是IO呢? monad 上的(混淆的)失败概念是神奇的,并且没有在类型级别表示,换句话说,没有明确的失败值。

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

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

这是您可以根据博客执行的操作的示例 post 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

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

我的 cabal build-depends: 设置:

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