我正在尝试使用免费的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}}?
答案 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
实现不自动分发>>=
的语义。 (这是我更喜欢我的操作库而不是免费库的主要原因。)