使用 Logic monad 在 ExceptT 抛出异常时回溯
Using Logic monad to backtrack upon exception thrown with ExceptT
我想使用 Logic
monad 来确保抛出错误的代码(在包含 ExcepT
的 monad 堆栈中)在抛出错误时回溯。这是一个简单的例子:
newtype FooT m a = FooT { unFooT :: ExceptT String m a }
deriving (Functor, Applicative, Monad, MonadTrans, MonadError String, MonadFail)
foo :: FooT Logic String
foo = do
let validate s = if s == "cf" then return s else throwError "err"
let go = do
first <- msum (map return ['a', 'b', 'c'])
second <- msum (map return ['d', 'e', 'f'])
traceM $ "Guess: " ++ [first, second]
validate [first, second]
go `catchError` const mzero
testfoo :: IO ()
testfoo = do
let r = observe $ runExceptT $ unFooT foo
case r of
Left e -> print $ "Error: " ++ e
Right s -> print $ "Result: " ++
这不是回溯;它没有产生任何结果。
我可以通过取消选择操作使其回溯(即,使用 lift (msum ...)
而不是现在的普通 msum
调用)。
但是,出于各种原因,我希望能够在 ExceptT
monad 中编写代码,并且基本上只是将 MonadPlus
实例从 Logic monad 提升到转换后的版本中。
我试图在这里编写一个自定义 MonadPlus
实例来完成此操作:
instance MonadPlus m => MonadPlus (FooT m) where
mzero = lift mzero
mplus (FooT a) (FooT b) = lift $ do
ea <- runExceptT a
case ea of
Left _ -> do
eb <- runExceptT b
case eb of
Left _ -> mzero
Right y -> return y
Right x -> do
eb <- runExceptT b
case eb of
Left _ -> return x
Right y -> return x `mplus` return y
相同的代码也适用于 Alternative
实例。但是,这实际上并没有帮助;它仍然没有回溯。这个实例有问题吗?有没有更好的方法来解决这个问题?我是在尝试做一些没有意义的事情吗?在一天结束的时候,我总是可以举起所有东西,但我宁愿避免这样做。
编辑:
一直在胡闹一些。如果我使用 mplus
,我上面的 MonadPlus
实例可以工作,但是如果我像上面那样使用 msum
, 不会 工作...
使用 MonadPlus
/Alternative
的实例:
instance (Alternative m, Monad m) => Alternative (FooT m) where
empty = FooT (ExceptT empty)
FooT (ExceptT a) <|> FooT (ExceptT b) = FooT (ExceptT (a <|> b))
注意:Alternative
和 MonadPlus
是多余的,所以只实现 Alternative
并使用 Data.Foldable.asum
而不是 msum
更简单。
您实现的那个与 ExceptT
上已有的没有太大区别,并且没有真正使用 Alternative m
实例。确保使用专用于 m
的 (<|>)
以从回溯中受益。
我想使用 Logic
monad 来确保抛出错误的代码(在包含 ExcepT
的 monad 堆栈中)在抛出错误时回溯。这是一个简单的例子:
newtype FooT m a = FooT { unFooT :: ExceptT String m a }
deriving (Functor, Applicative, Monad, MonadTrans, MonadError String, MonadFail)
foo :: FooT Logic String
foo = do
let validate s = if s == "cf" then return s else throwError "err"
let go = do
first <- msum (map return ['a', 'b', 'c'])
second <- msum (map return ['d', 'e', 'f'])
traceM $ "Guess: " ++ [first, second]
validate [first, second]
go `catchError` const mzero
testfoo :: IO ()
testfoo = do
let r = observe $ runExceptT $ unFooT foo
case r of
Left e -> print $ "Error: " ++ e
Right s -> print $ "Result: " ++
这不是回溯;它没有产生任何结果。
我可以通过取消选择操作使其回溯(即,使用 lift (msum ...)
而不是现在的普通 msum
调用)。
但是,出于各种原因,我希望能够在 ExceptT
monad 中编写代码,并且基本上只是将 MonadPlus
实例从 Logic monad 提升到转换后的版本中。
我试图在这里编写一个自定义 MonadPlus
实例来完成此操作:
instance MonadPlus m => MonadPlus (FooT m) where
mzero = lift mzero
mplus (FooT a) (FooT b) = lift $ do
ea <- runExceptT a
case ea of
Left _ -> do
eb <- runExceptT b
case eb of
Left _ -> mzero
Right y -> return y
Right x -> do
eb <- runExceptT b
case eb of
Left _ -> return x
Right y -> return x `mplus` return y
相同的代码也适用于 Alternative
实例。但是,这实际上并没有帮助;它仍然没有回溯。这个实例有问题吗?有没有更好的方法来解决这个问题?我是在尝试做一些没有意义的事情吗?在一天结束的时候,我总是可以举起所有东西,但我宁愿避免这样做。
编辑:
一直在胡闹一些。如果我使用 mplus
,我上面的 MonadPlus
实例可以工作,但是如果我像上面那样使用 msum
, 不会 工作...
使用 MonadPlus
/Alternative
的实例:
instance (Alternative m, Monad m) => Alternative (FooT m) where
empty = FooT (ExceptT empty)
FooT (ExceptT a) <|> FooT (ExceptT b) = FooT (ExceptT (a <|> b))
注意:Alternative
和 MonadPlus
是多余的,所以只实现 Alternative
并使用 Data.Foldable.asum
而不是 msum
更简单。
您实现的那个与 ExceptT
上已有的没有太大区别,并且没有真正使用 Alternative m
实例。确保使用专用于 m
的 (<|>)
以从回溯中受益。