为什么异常没有被 `catch` 捕获?

Why exception is not catching by `catch`?

为什么不捕获转换异常 conv B3 ?!

import qualified Control.Monad.Catch as E
data A = A1|A2 deriving (Enum, Show)
data B = B1|B2|B3 deriving (Enum, Show)
conv b = safeConv
  where
    catchError e = Left e
    safeConv = (Right $ (toEnum $ fromEnum b :: A)) `E.catch` catchError

我得到了:

Right *** Exception: toEnum{A}: tag (2) is outside of enumeration's range (0,1)
CallStack (from HasCallStack):
  error, called at xxx.hs:227:26 in main:Main

Haskell 异常与 Java 或 C++ 有所不同:"true" 异常在 IO monad 中工作,然后通过以下方式模仿异常纯粹的意思,如 ExceptT.

toEnum 函数抛出第一种 - IO 异常, - 不能用纯代码捕获。他们飞到最近的 IO 地方,在你的情况下显然是 GHCi。

为了捕获此类异常,首先需要通过Control.Exception.evaluate. Then you can catch such exceptions with catch, or, if you just want to convert it to an Either exception A (as you seem to be doing), there is an app for that! - it's called try.

将抛出表达式包裹在IO

此外,当使用 catchtry 时,您需要指定要捕获的异常的特定 类型 。但是 it is possible to catch all exceptions 无论类型如何,都使用存在类型 SomeException.

所以,总结所有这些,我们得到这个代码:

import qualified Control.Exception as E

data A = A1|A2 deriving (Enum, Show)
data B = B1|B2|B3 deriving (Enum, Show)

conv :: Enum b => b -> IO (Either E.SomeException A)
conv b = E.try . E.evaluate . toEnum $ fromEnum b

注意 1conv 上的类型注释是必要的,以便将 E.SomeException 指定为要捕获的异常类型。没有它,GHC 会抱怨异常类型不明确。

NOTE 2:因为我们在conv上的类型注解已经指定了目标类型A,所以在toEnum $ fromEnum b上的类型注解是no需要更长的时间。

注意 3:我已将您导入的 Control.Monad.Catch 替换为 Control.Exception,因为那是 evaluateSomeException 的位置是。

如果有人需要解决方案,我会把它留在这里。为了保持函数的纯净,转换应该是:

unsafeConvEnum :: (Enum a, Enum b) => a -> b
unsafeConvEnum = toEnum . fromEnum

convEnum :: (Enum a, Enum b) => a -> Maybe b
convEnum e = unsafePerformIO conv'
  where onError (_::SomeException) = pure Nothing
        conv' = (Just <$> evaluate (unsafeConvEnum e)) `catch` onError

没有任何 IO :)