You can use liftM2
from Control.Monad
:
liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
> :t liftM2 (++)
liftM2 (++) :: Monad m => m [a] -> m [a] -> m [a]
Alternatively, you could use do
notation:
(+++) :: Monad m => m [a] -> m [a] -> m [a]
ms1 +++ ms2 = do
s1 <- ms1
s2 <- ms2
return $ s1 ++ s2
Both of these are equivalent. In fact, the definition for liftM2
is implemented as
liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
liftM2 f m1 m2 = do
val1 <- m1
val2 <- m2
return $ f val1 val2
Very simple! All it does is extract the values from two monadic actions and apply a function of 2 arguments to them. This goes with the function liftM
which performs this operation for a function of only one argument. Alternatively, as pointed out by others, you can use IO
's Applicative
instance in Control.Applicative
and use the similar liftA2
function.
You might notice that generic Applicative
s have similar behavior to generic Monad
s in certain contexts, and the reason for this is because they're mathematically very similar. In fact, for every Monad
, you can make an Applicative
out of it. Consequently, you can also make a Functor
out of every Applicative
. There are a lot of people excited about the Functor-Applicative-Monad proposal that's been around for a while, and is finally going to be implemented in an upcoming version of GHC. They make a very natural hierarchy of Functor > Applicative > Monad
.