I've written the following function (using the these library):
import Control.Applicative (Alternative ((<|>)), optional)
import Data.These (These(..))
asumThese :: (Alternative f, Monad f) => f a -> f b -> f (These a b)
asumThese x y = optional x >>= \case
Just x' -> maybe (This x') (These x') <$> optional y
Nothing -> That <$> y
Is there a way I can remove the Monad constraint, or is there no way out of using bind?
My gut feeling says I can't avoid the Monad constraint here, but can anyone give me an intuition of why that is the case? Just so in the future I have a better sense of what sort of functions I likely need Monad and which ones I can generalise to Applicative and/or Alternative?
I sense it has something to do with needing Monad for sequencing, but I can't quite put my finger on it, because f <$> x <*> y parses things in a different sequence to f <$> y <*> x, so there's still sequencing in Applicative.
You need Monad m over Applicative m specifically when the action of one step depends on the return value of a previous step. The dependency of control on data is considered to capture the power of "sequencing". If you write (,) <$> x <*> y, that x executes/parses/whatevers before y is only a convention. For the right Applicative, y could have its effect before x, or y and x could be in parallel. In x >>= maybe (return Nothing) (fmap Just . y), it's not known whether y will execute until x has already finished. For >>=, sequencing is forced, not merely conventional.
In this case control does depend on data: if optional x returns Nothing, the next action is to run y, but if it returns Just x', the next action is optional y. That is, you only introduce a branch to allow y to fail if x succeeds. Thus, I don't think there's a (good) way to write this operation purely in terms of Alternative.
There is of course the really simple
asumThese x y = (These <$> x <*> y) <|> This <$> x <|> That <$> y
but this i) produces results in a different order (i.e. it introduces the Alternative effects differently, since the effects can't depend on the data anymore) and ii) is inefficient.
I think you may be looking for selective functors, an abstraction between applicative functors and monads.
import Control.Applicative
import Control.Selective
import Data.These
asumThese :: (Alternative f, Selective f) => f a -> f b -> f (These a b)
asumThese x y = branch
(Left <$> x <|> pure (Right ()))
((\ b a -> maybe (This a) (These a) b) <$> optional y)
(const . That <$> y)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With