我正在尝试使用一个免费的 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 节点转换为约束求解器中的具体约束,而不会导致我希望求解器处理的备选方案数量的组合爆炸。
in Control.MonadPlus.Free,Plus ms >>= f导致f应用于Pure每个 monad 下的每个叶子ms。这是必要的,因为它可能会为它替换f的每个叶子产生不同的值。Pure
但是,在 中Plus ms >> g,g不受 的任何叶子影响ms,因此将其分布在 中Plus似乎没有必要。
通过反复试验,我发现我可以Control.MonadPlus.Free使用新的Then构造函数来扩展 monad:
data Free f a = Pure a
| Free (f (Free f a))
| Then [Free f ()] (Free f a)
| Plus [Free f a]
在这里,新的Then构造函数包含一系列我们忽略其值的 monad,然后是产生实际值的最终 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 替换为新的。我知道将新 monad 附加到 的效率低下,但我认为它与将其新 monad 缝合到链的末尾一样糟糕(并且整个事情可以使用延续来重写)。Pure aPure ()++>>=fmap
这似乎是一件合理的事情吗?这是否违反单子定律(这有关系吗?),还是有更好的方法来使用现有的Control.Monad.Free?