8

I want to sequentially compose two monad actions in Haskell, discarding any value produced by the second, and passing the argument to both actions. Currently I'm using a do-block like this:

ask = do
  result <- getLine
  putStrLn result
  return result

I was hoping to write this a little more point free and neat, so I tried this:

ask' = getLine <* putStrLn

However, this doesn't even type check and the problem is that <* does not transfer the result of the first action to the second. I want to chain the actions like >>= does, but not change the result. The type should be (a -> m b) -> (a -> m c) -> (a -> m b), but Hoogle yields no suitable results. What would be an operator to achieve this function composition?

amoebe
  • 4,857
  • 5
  • 37
  • 42
  • 1
    So you want `getLine >>= \x -> putStrLn x >> return x` in point free style. Have you asked lambdabot? It says `liftM2 (>>) putStrLn return =<< getLine`. – Thomas M. DuBuisson Dec 08 '15 at 17:29
  • @ThomasM.DuBuisson It seems like Lambdabot found almost the exact function that OP wanted! `flip (liftM2 (>>)) :: Monad m => (a -> m b) -> (a -> m c) -> (a -> m b)` - the actual type is slightly more generalized so it is somewhat hard to see. – user2407038 Dec 08 '15 at 18:05
  • @ThomasM.DuBuisson Thank you, I asked the `pointfree` command line tool, and it could not handle `do`, so I gave up. I ended up using `ask = getLine >>= liftM2 (>>) putStrLn return`, which looks okay, thanks again! You can put this into an answer if you want, then I can mark it as solved. – amoebe Dec 08 '15 at 19:44

3 Answers3

9

As a tendency, if you use one value in two different places it probably is a good idea to give it a name in a clear do block, rather than pressing on pointless style.

The abstract concept of splitting up information flow to different actions is captured by cartesian monoidal categories, known to Haskellers as arrows. In your case, you're basically working in the IO Kleisli category:

import Prelude hiding (id)
import Control.Arrow

ask' :: Kleisli IO () String
ask' = Kleisli (\()->getLine) >>> (putStrLn &&& id) >>> arr snd

I don't think it's a good idea to write such code.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • You're right, it is probably not a very good idea. The arrows look really complicated. I'm not sure if the solution with `liftM2` in clear enough or confusing? – amoebe Dec 08 '15 at 19:52
  • The problem with `liftM2(>>)` is that it mixes two different monads (`(a->)` and `IO`) in a really quite convoluted way. If you do something like that, better do it properly with `ReaderT`, but that's only worth it if you're reading the same value from a whole bunch of places. – leftaroundabout Dec 08 '15 at 22:32
2

I want to sequentially compose two monad actions in Haskell, discarding any value produced by the second, and passing the argument to both actions.

This sounds to me like a Reader—the function type r -> m a is isomorphic to ReaderT r m a, and the monad works by implicitly plugging in the same r value into all the "holes." So for example:

import Control.Applicative
import Control.Monad.Reader

example :: IO String
example = getLine >>= discarding putStrLn

discarding :: Monad m => (a -> m b) -> a -> m a
discarding action = runReaderT (ReaderT action *> ask)

The operator you want is then something like:

action `thingy` extra = action >>= discarding extra

But of course discarding has a simpler implementation:

discarding :: Applicative f => (a -> f b) -> a -> f a
discarding action a = action a *> return a

...so in the end I think this is really code golf. But in a more complex program where this is a common pattern at a larger scale it might be worth a shot. Basically, if you have:

a0 :: r -> m a0
a1 :: r -> m a1
   .
   .
   .
an :: r -> m an

Then it follows that:

ReaderT a0 :: ReaderT r m a0
ReaderT a1 :: ReaderT r m a1
   .
   .
   .
ReaderT an :: ReaderT r m an

And then:

runReaderT (ReaderT a0 <* ReaderT a1 <* ... <* ReaderT an) :: r -> m a0
Luis Casillas
  • 29,802
  • 7
  • 49
  • 102
2

For completeness, in this particular case (the IO) monad, you could also abuse bracket for this purpose:

bracket getLine putStrLn return

But I strongly discourage it, as this will be much less readable than the original do-notation block, it's just ugly.

As already mentioned, in this particular case naming the result seems the best way.

See also Should do-notation be avoided in Haskell?

Community
  • 1
  • 1
Petr
  • 62,528
  • 13
  • 153
  • 317