Yury Sukhoverkhov
Yury Sukhoverkhov

Reputation: 450

How to chain a -> IO (m b) functions

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)

Upvotes: 1

Views: 496

Answers (2)

Koray Al
Koray Al

Reputation: 322

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

Upvotes: 2

Alec
Alec

Reputation: 32319

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

Upvotes: 6

Related Questions