Control.MonadPlus.Free没有不必要的分发

时间:2013-10-04 21:58:59

标签: haskell monads free-monad

我正在尝试使用免费的monad来构建一个EDSL,用于构建像Prolog这样的AND / OR决策树,>>=映射到AND,mplus映射到OR。我希望能够描述类似A AND (B OR C) AND (D OR E)的内容,但我不希望分配将其转换为(A AND B AND D) OR (A AND B AND E) OR (A AND C AND D) OR (A AND C AND E)。最后,我想将AND / OR节点转换为约束求解器中的具体约束,而不会导致我希望求解器处理的替代数量的组合爆炸。

Control.MonadPlus.Free Plus ms >>= f中,f会将Pure应用于ms中每个monad下的每个f树叶。这是必要的,因为Pure可能会为它替换的每个Plus ms >> g叶子产生不同的值。

但是,在g ms Plus中,Control.MonadPlus.Free不会受Then的任何影响,因此将其分发到data Free f a = Pure a | Free (f (Free f a)) | Then [Free f ()] (Free f a) | Plus [Free f a] 似乎是不必要的。

通过反复试验,我发现我可以使用新的Then构造函数扩展Monad monad:

instance Functor f => Monad (Free f) where
    return = Pure

    Pure a >>= f = f a
    Free fa >>= f = Free $ fmap (>>= f) fa
    Then ms m >>= f = Then ms $ m >>= f
    Plus ms >>= f = Plus $ map (>>= f) ms

    Pure a >> mb = mb
    Then ms ma >> mb = Then (ms ++ [ma >>= (const $ return ())]) mb
    ma >> mb = Then [] ma >> mb

这里,新的>>构造函数包含一系列monad,我们忽略其值,然后是产生实际值的最终monad。新的Pure a实例如下所示:

Pure ()

++运算符通过将>>=替换为fmap来覆盖任何现有的叶子,将封顶的monad附加到列表中,并将值monad替换为新值。我知道用Control.Monad.Free附加新monad的效率低下,但我认为它与{{1}}用{{1}}将其新monad拼接到链的末尾一样糟糕(和整个事情可以使用延续来重写。

这看起来像是一件合理的事吗?这是否违反了monad法律(这有关系吗?),还是有更好的方法来使用现有的{{1}}?

1 个答案:

答案 0 :(得分:2)

您可能需要查看我的operational包,这是对免费monad的不同看法。

特别要看一下BreadthFirstParsing.hs示例。它具有mplus操作,因此>>= 会自动在其上分发。这允许您以广度优先的方式实现解析器组合器。

翻译为Control.Monad.Free,重点是如果你使用仿函数

data F b = MZero | MPlus b b

然后Free F会自动将>>=分发到mplus。你必须使用仿函数

data F b = MZero | forall a. MPlus (Free f a) (Free f a) (a -> b)

相反,如果您想为MPlus实现不自动分发>>=的语义。 (这是我更喜欢我的操作库而不是免费库的主要原因。)