gpt4 book ai didi

haskell - 如何处理链式 IO 中间的错误?

转载 作者:行者123 更新时间:2023-12-02 10:41:19 25 4
gpt4 key购买 nike

假设我有一个 readEnv 函数,它读取两个环境变量并返回 Either 值,并使用 ReadError 类型作为 Left 值:

module Main where

import Control.Exception (SomeException(..), handle, throw)
import Data.Typeable (typeOf)
import System.Environment (getEnv)

data ReadError
= MissingHost
| MissingPort
deriving (Show)

main :: IO ()
main = do
eitherEnvs <- readEnv'
case eitherEnvs of
Left err -> print err
Right (port, host) -> print (port, host)

readEnv :: IO (Either ReadError (String, String))
readEnv = do
port <- getEnv "HOST"
host <- getEnv "PORT"
return $ Right (port, host)

readEnv' :: IO (Either ReadError (String, String))
readEnv' = handle missingEnv readEnv

missingEnv :: SomeException -> IO (Either ReadError (String, String))
missingEnv (SomeException e)
| isMissingHost e = do
print e
return $ Left $ MissingHost
| isMissingPort e = do
print e
return $ Left $ MissingPort
| otherwise = throw e
where
isMissingHost e = take 4 (show e) == "HOST"
isMissingPort e = take 4 (show e) == "PORT"

因为我知道 getEnv 是一个 IO,如果缺少 env var,它会抛出异常(我知道有lookupEnv,但我的问题是如何处理错误,而不是如何避免错误) ,我创建了一个 readEnv' 函数,它将捕获 IO 异常并将其转换为 ReadError 类型。

上面的代码可以工作,但是,我不喜欢这种模式/样式来处理异常,因为为了处理来自 getEnv "HOST" 的异常,我必须将处理程序放在外面整个readEnv,并解析错误消息以区分错误是MissingHost还是MissingPort。如果错误消息不包含“HOST”或“PORT”,则 missingEnv 无法区分异常来自哪个 getEnv 调用。

理想情况下,有一种方法可以在发生异常的地方处理异常并短路以返回 Left 值。因为我知道 getEnv "HOST" 中唯一的 IOException 是 MissingHost 错误,所以我不需要解析错误消息。

如何做到这一点?

最佳答案

考虑使用 exceptT monad 并向 getEnv 添加正确的抽象。

例如:

首先让我们跳过样板:

module Main where

import Control.Exception (SomeException(..), handle, throw)
-- N.B. Should use Control.Exception.Safe
import qualified Control.Exception as X
import Data.Typeable (typeOf)
import qualified System.Environment as Env
import Control.Monad.Trans.Except

我们想要定义类似 IO 的东西,但专门用于以更可组合的方式处理异常,并且至少允许 getEnv。 monad 是 exceptT IO:

type MyIO a = ExceptT ReadError IO a
runMyIO :: MyIO a -> IO (Either ReadError a)
runMyIO = runExceptT

我们可以在 monad 中执行的操作应该被提升 - 请记住,如果你的代码的其余部分经常输入 lift 那么你可能没有正确抽象你的 monad。

getEnv :: String -> MyIO String
getEnv s = ExceptT ((Right <$> Env.getEnv s) `X.catch` hdl)
where hdl :: X.SomeException -> IO (Either ReadError String)
hdl _ = pure $ Left (Missing s)

现在我们可以在 main 中使用这个版本的 getEnv:

main :: IO ()
main = do
eitherEnvs <- runMyIO ( (,) <$> getEnv "HOST" <*> getEnv "PORT" )
case eitherEnvs of
Left err -> print err
Right (port, host) -> print (port, host)

是的,我们确实重新定义了错误类型:

data ReadError
= Missing String
-- N.B an enum strategy such as MissingPort is doable but often has a
-- data-dependency at the call site such as @getEnv "host" MissingHost@
--
-- That would be a lot like your 'missingEnv' function which forms a mapping
-- from user strings to the ADT enum 'ReadError'.
deriving (Show)

关于haskell - 如何处理链式 IO 中间的错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53395955/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com