This is way overthinking the question, but...
In your code, the types of each branch of the Either
might be distinct, but they don't escape the do-block, because they are "erased" by the Left
and Right
continuations.
That looks a bit like an existential type. Perhaps we could declare a type which packed the initial action along with its continuation, and give that type an Alternative
instance.
Actually, we don't have to declare it, because such a type already exists in Hackage: it's Coyoneda
from kan-extensions.
data Coyoneda f a where
Coyoneda :: (b -> a) -> f b -> Coyoneda f a
Which has the useful instances
Alternative f => Alternative (Coyoneda f)
MonadPlus f => MonadPlus (Coyoneda f)
In our case the "return value" will be itself a monadic action m
, so we want to deal with values of type Coyoneda m (m a)
where m a
is the type of the overall do-block.
Knowing all that, we can define the following function:
sandwich :: (Foldable f, MonadPlus m, Monad m)
=> m x
-> f (Coyoneda m (m a))
-> m a
sandwich more = join . lowerCoyoneda . hoistCoyoneda (<* more) . asum
Reimplementing the original example:
sandwich more [Coyoneda m xCont, Coyoneda n yCont]