1

There are functions with signatures like:

a -> IO (m b)
b -> IO (m c)
c -> IO (m d)

How do I chain them in

a -> IO (m d)

?

Practical application: say there a set of REST endpoints. Each of the return a value and next one is requires the value returned by previous as an argument.

So functions for fetching from the endpoints are like:

Value1 -> IO (Maybe Value2)
Value2 -> IO (Maybe Value3)
Value3 -> IO (Maybe Value4)
Alec
  • 31,829
  • 7
  • 67
  • 114
  • 1
    You are looking for Kliesli composition `(>=>)` Check here http://hackage.haskell.org/package/base-4.9.1.0/docs/Control-Monad.html#v:-62--61--62- – zeronone Feb 06 '17 at 08:34
  • 1
    Additionally `IO (Maybe a) ~ MaybeT IO a` and `MaybeT` is defined in http://hackage.haskell.org/package/transformers-0.5.2.0/docs/Control-Monad-Trans-Maybe.html#v:MaybeT – zeronone Feb 06 '17 at 08:36

2 Answers2

6

There are functions with signatures like:

a -> IO (m b)
b -> IO (m c)
c -> IO (m d)

How do I chain them in

a -> IO (m d)

In general, you might not be able to. For example, if m is Const, I'm not sure asking this even makes sense. In general, you probably expect m to be a Monad. However, note that even if m is a Monad, its composition with IO may not be (see this).

Value1 -> IO (Maybe Value2)
Value2 -> IO (Maybe Value3)
Value3 -> IO (Maybe Value4)

Ah, Now you are talking! The abstraction you are looking for here is MaybeT and Kleisli composition (>=>). Then, for example,

import Control.Monad ((>=>))
import Control.Monad.Trans.Maybe (MaybeT(..))

rest1 :: Value1 -> IO (Maybe Value2)
rest2 :: Value2 -> IO (Maybe Value3)
rest3 :: Value3 -> IO (Maybe Value4)

rest4 :: Value1 -> IO (Maybe Value4)
rest4 x = runMaybeT ((MaybeT . rest1 >=> MaybeT . rest2 >=> MaybeT . rest3) x)

That still looks a bit ugly. The thing to do is probably to refactor your rest1, rest2, and rest3 functions. As has been pointed out in the comments MaybeT IO a can be converted to and from IO (Maybe a) (in fact that is exactly what runMaybeT and MaybeT do).

import Control.Monad ((>=>))
import Control.Monad.Trans.Maybe (MaybeT(..))

rest1 :: Value1 -> MaybeT IO Value2
rest2 :: Value2 -> MaybeT IO Value3
rest3 :: Value3 -> MaybeT IO Value4

rest4 :: Value1 -> MaybeT IO Value4
rest4 = rest1 >=> rest2 >=> rest3
Community
  • 1
  • 1
Alec
  • 31,829
  • 7
  • 67
  • 114
  • After refactoring, would it make sense to regeneralize the `rest` functions, to recapture the flavor of the original functions? E.g., `rest1 :: MonadTrans mt => Value1 -> mt IO Value2`? – chepner Feb 06 '17 at 14:12
  • @chepner I would definitely say yes; though I'm not sure your particular type makes sense. Something like `rest1 :: (MonadIO m, MonadPlus m) => Value1 -> m Value2` would express that `rest1` may do some IO (`MonadIO`) and may not succeed (`MonadPlus`). – Daniel Wagner Feb 06 '17 at 18:57
2

if m has an instance of Traversable (Maybe has one) here is another alternative:

import           Control.Monad (join)

f :: (Traversable m, Monad m)
  => (a -> IO (m b))
  -> (b -> IO (m c))
  -> (c -> IO (m d))
  -> a
  -> IO (m d)
f fa fb fc a = fa a
           >>= traverse fb
           >>= fmap join . traverse fc . join
Koray Al
  • 322
  • 3
  • 10
  • Looks like you don’t need the `liftIO`, and `>>= return . join` is `fmap join`. – Jon Purdy Feb 06 '17 at 10:16
  • for the part with `>>= return . join` I wanted to separate the final step. so if one wanted to add more steps (ie. `d -> IO (m e)` ) to this example, they would just add another line with `>>= traverse fd . join` before the last line. – Koray Al Feb 06 '17 at 12:32