开发者

Join two IO actions using the same input in Haskell

I have two functions:

emptyDirectory, copyStubFileTo :: FilePath -> IO ()

I want to combine them the way as below:

forM_ ["1", "2"] $\n -> do
  emp开发者_如何学PythontyDirectory 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


You can try something like mapM_:

mapM_ ($input) [list of functions]

So in your case:

mapM_ forM_ [emptyDirectory, copyStubFileTo] . flip ($) $ ["1", "2"] 

Untested. But should be similar.


I'm still not sure I understand this entirely, but here it goes. (basically, I stole this idea from FUZxxl but simplified and explained it)

mapM_ (forM_ [list of inputs]) [list of functions]

For example

mapM_ (forM_ ["foo", "bar"]) [putStrLn, putStrLn . reverse]

Produces

foo
bar
oof
rab

This can be generalized

xs >.> fs = mapM_ (forM_ xs) fs

So this would produce the same output

main = ["foo","bar"] >.> [putStrLn, putStrLn . reverse]

Plus, >.> is a funny face.

Basically, mapM_ takes a function and a list, and applies the function to each member of the list. So we create the function (forM_ ["foo", "bar"]) which has the type (Monad m) => (String -> m b) -> m (). It expects a function to be input. Lucky for us, each member of the second list is a function! And so each function in the second list will be fed to this higher-order function, producing the desired effect.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜