用变压器除外评估自由单子

时间:2015-10-30 12:23:18

标签: haskell

我正在尝试向我的免费monad添加异常处理,但我无法捕获评估者抛出的任何内容。到目前为止,我添加了Except,就像我有其他变形金刚一样:

data TestFunctor a = Test a deriving(Functor)

runTest :: FT TestFunctor (Except Int) a -> Either Int a
runTest = runIdentity . runExceptT . iterT evalTest

evalTest :: TestFunctor (Except Int a) -> Except Int a
evalTest (Test next) = throwError 2 *> next

但在运行时:

> runTest (catchError (throwError 0) (const $ pure ()))
Right ()
> runTest (catchError (liftF $ Test ()) (const $ pure ()))
Left 2

有没有办法在评估者的免费monad catch错误中允许表达式?

我正在使用Control.Monad.Trans.Free.Church模块。

1 个答案:

答案 0 :(得分:4)

构建FT TestFunctor (Except Int)时,catchError会在您开始使用runTest进行解释之前发生。如果我们暂时切换到FreeT,我们可以看到发生了什么。我们将评估以下所有内容:

            throwError 0                      :: FreeT TestFunctor (Except Int) ()
catchError (throwError 0) (const $ pure ())   :: FreeT TestFunctor (Except Int) ()
            liftF $ Test ()                   :: FreeT TestFunctor (Except Int) ())
catchError (liftF $ Test ()) (const $ pure () :: FreeT TestFunctor (Except Int) ())

当我们抛出错误时,我们会将错误包含在FreeT

>             throwError 0                      :: FreeT TestFunctor (Except Int) ()
FreeT (ExceptT (Identity (Left 0)))

捕获错误会将其替换为免费树

> catchError (throwError 0) (const $ pure ())   :: FreeT TestFunctor (Except Int) ()
FreeT (ExceptT (Identity (Right (Pure ()))))

另一方面,当我们liftF Test ()时,它会返回到Right中的ExceptT,就像它所拥有的()一样。

>             liftF $ Test ()                   :: FreeT TestFunctor (Except Int) ())
FreeT (ExceptT (Identity (Right (Free (Test (FreeT (ExceptT (Identity (Right (Pure ()))))))))))
                          | The one above is a return, the one below is a throw
FreeT (ExceptT (Identity (Left 0)))

当你catchError这个时,它没有做任何事情,它已经成功了。

> catchError (liftF $ Test ()) (const $ pure () :: FreeT TestFunctor (Except Int) ())
FreeT (ExceptT (Identity (Right (Free (Test (FreeT (ExceptT (Identity (Right (Pure ()))))))))))

将操作添加到树

如果您想在自由树的评估中抛出并捕获错误,请将throw和catch操作添加到您的仿函数可以表示的内容中。

data ExceptOp e a
  = Throw e
  | Catch a (e -> a)
  deriving (Functor)

要解释支持抛出和捕获异常的操作,我们将Throw替换为throwError,将Catch替换为catchError

runTest :: FT (ExceptOp Int) Identity a -> Either Int a
runTest = runIdentity . runExceptT . iterTM evalExcept

evalExcept :: (Monad m) => ExceptOp e (ExceptT e m a) -> ExceptT e m a
evalExcept (Throw e) = throwError e
evalExcept (Catch next f) = catchError next f

这会在我们评估时捕获异常

> runTest (wrap $ Catch (liftF $ Throw 0) (const $ pure ()))
Right ()