在Exc成员是MonadPlus实例的情况下,为Control.Eff创建MonadPlus实例

3

单子变换器中,我们有

instance (Monad m, Monoid e) => MonadPlus (ExceptT e m)

可扩展效果 中,不存在这样的东西。
instance (Monoid e) => MonadPlus (Eff (Exc e :> r))

我已经尝试过实现它,但都失败了。这是我目前的进展:

instance (Monoid e) => MonadPlus (Eff (Exc e :> r)) where
  mzero = throwExc mempty
  a `mplus` b = undefined $ do
                  resultA <- runExc a
                  case resultA of
                    Left l -> runExc b
                    Right r -> return $ Right r

有2个问题:

  • for mzero, GHC complains as follows:

    Could not deduce (Monoid e0) arising from a use of ‘mempty’
      from the context (Monad (Eff (Exc e :> r)), Monoid e)
    

    Why doesn't GHC match e0 with e ?

    Answer (provided in comment): turn on ScopedTypeVariables

  • for mplus, undefined should be replaced with the inverse function of runExc, but I can't find it in the API of extensible-effects. Did I miss something ?

背景: 我希望在 Member (Exc e) r => Eff r a 中写入 a <|> b,意思是:

  • 尝试 a
  • 如果 a 抛出 ea,尝试 b
  • 如果 b 抛出 eb,则抛出 mappend ea eb

这需要一个 Alternative 实例,这就是我首先尝试实现 MonadPlus 实例的原因。

注意: 我使用的是 GHC 7.8.3。

非常感谢您的帮助。


2
为什么 GHC 不将 e0 与 e 匹配 - 打开 ScopedTypeVariables - user2407038
@user2407038,确实解决了第一个问题,谢谢! - koral
1个回答

4

我想你可能对可扩展效果和期望的 MonadPlus (Eff (Exc e :> r)) 实例存在疑惑。

如果您想构建一个非确定性运算并且还要抛出异常,无需创建新实例即可完成。我认为我定义了单独的 mzero' 和 mplus' 可能是混淆的部分原因,它们与 MonadPlus 中的那些完全等效。无论如何,由于这种等价性,您可以简单地编写

instance Member Choose r => MonadPlus (Eff r) where
    mzero = mzero'
    mplus = mplus'

该实例表示:具有Choose效果的计算是MonadPlus计算的一个示例,需要强调“among others”部分。该计算可能具有其他效果,例如抛出异常。上面的MonadPlus实例覆盖了该情况以及所有其他情况。
因此,要使用非确定性和异常,您只需使用mplus、mzero(或mplus'、mzero')以及throwExc即可。无需定义任何新实例——这与Monad Transformers形成鲜明对比。
当然,您必须决定异常如何与非确定性交互:异常是否应放弃所有选择还是仅剩余选择?这取决于您如何排序处理程序,哪个效果首先被处理。此外,您可以编写Choose和Exc效果的处理程序(在异常发生时保留已做出的选择并丢弃其余选择——从而模拟Prolog的cut)。该库的代码(以及伴随论文的代码)中有相关示例。 针对修改后的问题的编辑: 如果您只需要<|>,则可以简单地实现它,无需使用MonadPlus或cut。该运算符仅是一种异常处理形式,并且可以作为两个catchError的组合来实现。以下是完整代码。
alttry :: forall e r a. (Typeable e, Monoid e, MemberU2 Exc (Exc e) r) =>
          Eff r a -> Eff r a -> Eff r a
alttry ma mb =
  catchError ma $ \ea ->
  catchError mb $ \eb -> throwError (mappend (ea::e) eb)

如果计算ma成功完成,它的结果将被返回。否则,会尝试mb;如果mb成功完成,则返回其结果。如果两者都失败,则会引发mappend异常。该代码直接匹配英语规范。
我们在签名中使用MemberU2而不是Member,以确保计算仅抛出一种类型的异常。否则,此构造不是很有用。我使用了原始实现Eff.hs。该文件还包含测试用例。
顺便说一下,使用可扩展效果时不需要定义或使用MonadPlus、MonadState等类型类。这些类型类旨在隐藏MonadTransformer堆栈的具体布局。使用可扩展效果,没有必要隐藏任何东西。那些扶助器不再需要。

好的,我需要更多的阅读和测试来理解 Control.Eff.Cut。与此同时,我已经更新了问题,并且我的根本目标是利用 Alternative 中的 <|> 运算符。Prolog 的剪枝模型能帮助我实现这个目标吗? - koral
关于“无需定义或使用MonadPlus,MonadState之类的类型类”:我理解Eff r a已经隐藏了底层联合类型r的具体元素,但我仍然有兴趣使用Monad*类型类进行名称重载。 a <|> ba \alttry` b`看起来更好,你不觉得吗?无论如何,非常感谢您详细的答复,这对我帮助很大。 - koral

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接