I would structure it like this: create two lists — one full A and one full B — so that creating list A might fail. You can build a Monoid that implements this logic and foldMap into it.
Since it is impossible to create a list of A s, we will need to build this Monoid on top of Maybe . The behavior that we want comes from the Maybe Applicative instance: if any of the mappend arguments is Nothing , then all this fails, otherwise we want to use mappend to combine the two results. This is a common recipe for combining Applicative and a Monoid . In particular:
newtype WrappedApplicative fa = Wrap { unWrap :: fa } instance (Applicative f, Monoid m) => Monoid (WrappedApplicative fm) where mempty = pure mempty Wrap x `mappend` Wrap y = Wrap $ liftA2 mappend xy
I don't know if this is newtype somewhere in base . It looked like it would be there, but I could not find it.
Without further ado, here Monoid we will foldMap ping in:
type Result = ([B], WrappedApplicative Maybe [A])
I borrow a (a, b) Monoid instance that delegates parallel A and B Monoid instances.
getAsOrToBs :: [Either AB] -> Either [A] [B] getAsOrToBs = fromResult . foldMap toResult where toResult (Left a) = ([aToB a], Wrap (Just [a])) toResult (Right b) = ([b], Wrap Nothing) fromResult (_, Wrap (Just as)) = Left as fromResult (bs, Wrap Nothing) = Right bs
Alternatively, with foldr :
getAsOrToBs :: [Either AB] -> Either [A] [B] getAsOrToBs = fromResult . foldr f ([], Just []) where f (Left a) (bs, mas) = (aToB a : bs, fmap (a:) mas) f (Right b) (bs, _) = (b:bs, Nothing) fromResult (_, Just as) = Left as fromResult (bs, Nothing) = Right bs
Look ma no partial functions!