这个问题想得太多了,但是...
在您的代码中,每个分支的类型Either
可能是不同的,但它们不会逃脱 do-block,因为它们被Left
andRight
延续“擦除”。
这看起来有点像存在主义类型。或许我们可以声明一个类型,它将初始动作和它的延续打包在一起,并给该类型一个Alternative
实例。
实际上,我们不必声明它,因为在 Hackage 中已经存在这样的类型:它Coyoneda
来自kan-extensions。
data Coyoneda f a where
Coyoneda :: (b -> a) -> f b -> Coyoneda f a
哪个有有用的实例
Alternative f => Alternative (Coyoneda f)
MonadPlus f => MonadPlus (Coyoneda f)
在我们的例子中,“返回值”本身就是一个 monadic action m
,所以我们要处理 type 的值Coyoneda m (m a)
wherem a
是整个 do-block 的类型。
知道了这一切,我们可以定义以下函数:
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]