我最近写了
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
绑定了许多变量,这就不太好了(