如何在MonadPlus/Alternative中合并并分支



我最近写了

do
e <- (Left <$> m) <|> (Right <$> n)
more actions
case e of
Left x -> ...
Right y -> ...

这看起来很尴尬。我知道protolude(和其他一些包(定义了

-- Called eitherP in parser combinator libraries
eitherA :: Alternative f => f a -> f b -> f (Either a b)

但即便如此,这一切都感觉有点手动。有没有我没见过的收紧的好图案?

我刚刚注意到OP在一条评论中表达了同样的想法。无论如何,我都会把我的想法发布出来。


Coyoneda是一个巧妙的技巧,但对于这个特定的问题来说,它有点过头了。我认为你所需要的只是常规的旧延续。

让我们将这些...命名为

do
e <- (Left <$> m) <|> (Right <$> n)
more actions
case e of
Left x -> fx x
Right y -> fy y

然后,我们可以将其写成:

do
e <- (fx <$> m) <|> (fy <$> n)
more actions
e

这有点微妙——在那里使用<$>很重要,尽管看起来你可能想使用=<<,这样第一行的结果实际上是稍后执行的一个单元操作,而不是立即执行的操作。

这是过度思考问题的方式,但是。。。

在您的代码中,Either的每个分支的类型可能是不同的,但它们不会逃脱do块,因为它们是";擦除";通过CCD_ 6和CCD_。

这看起来有点像存在主义。也许我们可以声明一个类型,该类型将初始操作及其延续打包,并为该类型提供一个Alternative实例。

实际上,我们不必声明它,因为Hackage中已经存在这样一个类型:它是来自kan扩展的Coyoneda

data Coyoneda f a where       
Coyoneda :: (b -> a) -> f b -> Coyoneda f a  

哪个有有用的实例

Alternative f => Alternative (Coyoneda f)
MonadPlus f => MonadPlus (Coyoneda f)

在我们的情况下;返回值";将本身为一元动作m,因此我们希望处理类型为Coyoneda m (m a)的值,其中m a是整个do块的类型。

知道了所有这些,我们可以定义以下函数:

sandwich :: (Foldable f, MonadPlus m, Monad m) 
=> m x 
-> f (Coyoneda m (m a)) 
-> m a
sandwich more = join . lowerCoyoneda . hoistCoyoneda (<* more) . asum 

重新实现原始示例:

sandwich more [Coyoneda m xCont, Coyoneda n yCont]

您也许可以这样做:

do
let acts = do more actions
(do x <- m; acts; ...) <|> (do y <- n; acts; ...)

我不知道你觉得这样是否更好。

(当然,如果这些more actions绑定了许多变量,这就不太好了(

最新更新