I have two functions:
emptyDirectory, copyStubFileTo :: FilePath -> IO ()
I want to combine them the way as below:
forM_ ["1", "2"] $\n -> do
emptyDirectory n
copyStubFileTo n
Is there any other standard way built into Haskell to simplify this combination? I mean joining two IO actions and giving them the same input.
liftA2 (>>) emptyDirectory copyStubFileTo
The short answer is that no, there's no standard way. The slightly longer answer is that you can write a >&>
combinator yourself:
(>&>) :: Monad m => (a -> m b) -> (a -> m c) -> a -> m c
(f >&> g) x = f x >> g x
This does exactly what you want it to do, as you can see from the types. Using this, you have emptyDirectory >&> copyStubFileTo :: FilePath -> IO ()
, and so
mapM_ (emptyDirectory >&> copyStubFileTo) ["1", "2"]
Hoogle doesn't turn up anything for the type signature, unfortunately, so I think it's safe to assume that it doesn't exist.
Now, this wasn't my original implementation of >&>
, because I originally looked at it in terms of a similar non-monadic combinator (&) :: (a -> b) -> (a -> c) -> a -> (b,c)
, which I find myself reimplementing with some regularity. If you approach this non-monadically and then generalize, you end up with what I think is a collection of useful combinators (which I keep reinventing) that don't seem to exist anywhere standard (even though I feel like at least some of them should). On the chance that you ever want some more general combinators, here they are; none of these seem to exist on Hoogle. (Choosing appropriate precedences is left as an exercise for the interested reader.)
To start, you want (&) :: (a -> b) -> (a -> c) -> a -> (b,c)
, which is the non-monadic version of what you want. You can't coalesce the b
and c
, since they're arbitrary types, so we return a tuple. There's only one sensible function of this type: (f & g) x = (f x, g x)
. If we feed this implementation through pointfree
, we get something nicer:
(&) :: Monad m => m a -> m b -> m (a,b)
(&) = liftM2 (,)
This works for functions because (r ->)
is a monad (the reader monad); (&)
captures the concept of doing two things and collecting both results, and for (r ->)
, "doing something" is evaluating a function.
With this, however, you'd have emptyDirectory & copyStubFileTo :: FilePath -> (IO (), IO ())
. Oog. We thus want to lift the monad out of the tuple, so we need a function tupleM :: Monad m => (m a, m b) -> m (a,b)
. Writing this ourselves, it uses the &
function from above:
tupleM :: Monad m => (m a, m b) -> m (a,b)
tupleM = uncurry (&)
If you look at the types, this actually makes sense, though it might take a few reads (it did for me).
Now, we can define a version of (&)
for monadic functions:
(<&>) :: Monad m => (a -> m b) -> (a -> m c) -> a -> m (b, c)
f <&> g = tupleM . (f & g)
We now have emptyDirectory <&> copyStubFileTo :: FilePath -> IO ((),())
, which is an improvement. But we really don't need that tuple (although we might for more interesting operations). Instead, we want (>&>) :: Monad m => (a -> m b) -> (a -> m c) -> a -> m c
(the analog of >>
), which is what we set out to define (and, in fact, defined above). Since I'm defining all sorts of combinators anyway, I'm going to define this by way of <.> :: Functor f => (b -> c) -> (a -> f b) -> a -> f c
(the functorial analogue of <=<
from Control.Monad).
(<.>) :: Functor f => (b -> c) -> (a -> f b) -> a -> f c
(f <.> g) x = f <$> g x
(>&>) :: (Monad m, Functor m) => (a -> m b) -> (a -> m c) -> a -> m c
f >&> g = snd <.> (f <&> g)
(If you don't like the Functor m
constraint, replace <$>
with `liftM`
.) What's most interesting about this is that we've arrived at a completely different implementation of >&>
. The first implementation focuses on the "do two things" aspect of >&>
; the second focus on the "evaluate two functions" aspect. This second one is actually the first implementation I thought of; I didn't try writing the first implementation, because I assumed it would be ugly. There's probably a lesson in that :-)
Disclaimer: I think your original code is very readable and there's nothing wrong with repeating the variable n
twice.
That said, if you want to get fancy, ((->) a)
forms an Applicative
instance, so you can do something like this:
import Control.Applicative
forM_ ["1", "2"] $ (>>) <$> emptyDirectory <*> copyStubFileTo
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