我正在尝试使用一个免费的 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 a
Pure ()
++
>>=
fmap
这似乎是一件合理的事情吗?这是否违反单子定律(这有关系吗?),还是有更好的方法来使用现有的Control.Monad.Free
?