Consider the following code:
data A
data B
f :: A -> B
f = undefined
data T = TA A | TB B
data ListT = ListTA [A] | ListTB [B]
g :: [T] -> ListT
g l =
let
f' :: T -> B
f' (TA x) = f x
f' (TB x) = x
isA :: T -> Bool
isA TA{} = True
isA TB{} = False
in
case (all isA l) of
True -> ListTA (map (\(TA x) -> x) l)
False -> ListTB (map f' l)
main = pure ()
The idea behind this is I've got a list of either A
s or B
s mixed together. I can convert A -> B
but not the other way around. Based on this list, I want to make either a list of A
s or list of B
s, the former if all my original list elements are A
s, the latter if at least one is a B
.
The above code compiles (and I'm guessing will work) but the incomplete pattern match in the map (\(TA x) -> x) l
makes me just a little uncomfortable. Is such an incomplete match just a necessity of what I'm doing here? Also, am I reinventing the wheel, is there something that generalises what I'm doing here?
The only way I can think of is something like
tryA :: [T] -> Maybe [A]
tryA [] = []
tryA (t:ts) =
case t of
TA x -> do xs <- tryA ts; return (x:xs)
TB _ -> Nothing
If tryA
returns nothing, then do map f' l
as before.
This way you're doing the all isA l
and the map
in a single pass, and it avoids an incomplete pattern.
I'd structure it like this: build two lists - one full of A
s and one full of B
s - with the effect that building the list of A
s could fail. One can build a Monoid
which implements this logic and foldMap
into it.
Since one could fail to build a list of A
s, we'll need to build this Monoid
on top of Maybe
. The behaviour we want comes from Maybe
's Applicative
instance: if either of mappend
's arguments is Nothing
then the whole thing fails, otherwise we want to use mappend
to combine the two results. This is a general recipe for combining an Applicative
and a Monoid
. Concretely:
newtype WrappedApplicative f a = Wrap { unWrap :: f a }
instance (Applicative f, Monoid m) => Monoid (WrappedApplicative f m) where
mempty = pure mempty
Wrap x `mappend` Wrap y = Wrap $ liftA2 mappend x y
I don't know if this newtype
is somewhere in base
. It seems like the sort of thing that would be there but I couldn't find it.
Without further ado, here's the Monoid
we'll be foldMap
ping into:
type Result = ([B], WrappedApplicative Maybe [A])
I'm borrowing (a, b)
's Monoid
instance, which delegates in parallel to a
and b
's Monoid
instances.
getAsOrToBs :: [Either A B] -> 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 A B] -> 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!
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