Jakob Sachs
Jakob Sachs

Reputation: 715

Compose IO Operations

I am trying to compose some IO wrapped functions.

My current code (that works) is:

getUserHome :: IO String
getUserHome = do
    usr_id <- getRealUserID
    homeDirectory <$> getUserEntryForID usr_id

What I am looking for is a more convenient notation (one without using the do-keyword).

Without any of the Monadic salad I would just write

getUserHome = homeDirectory . getUserEntryForID . getRealUserID

I would guess there is an alternative operator for the . that respects the Monad ..? But in all my searching, I have not found it.


I tried <$> but that does not seem to be what i want:

src/Main.hs:49:21: error:
    • Couldn't match type ‘IO UserEntry’ with ‘UserEntry’
      Expected type: UserID -> UserEntry
        Actual type: UserID -> IO UserEntry
    • In the second argument of ‘(<$>)’, namely ‘getUserEntryForID’
      In the first argument of ‘(<$>)’, namely
        ‘homeDirectory <$> getUserEntryForID’
      In the expression:
        homeDirectory <$> getUserEntryForID <$> getRealUserID
   |
49 |   homeDirectory <$> getUserEntryForID  <$> getRealUserID -- usr_id

Upvotes: 3

Views: 207

Answers (2)

leftaroundabout
leftaroundabout

Reputation: 120711

First always consider the types:

getRealUserID     ::              IO UserId
getUserEntryForID :: UserId    -> IO UserEntry
homeDirectory     :: UserEntry ->    String

Now you can:

  1. apply getRealUserID with getUserEntryForID. That's a direct fit for

    (>>=) :: m  a      -> (a      -> m  b        ) -> m  b
    (>>=) :: IO UserId -> (UserId -> IO UserEntry) -> IO UserEntry
    

    Actually I'd prefer the flipped version if you want this to look like a composition chain, i.e.

    getUserEntryForID =<< getRealUserID :: IO UserEntry
    
  2. Apply homeDirectory to this. That is in your case not monadic at all, so you need to lift it with fmap or <$>. Take operator precedence into account.

    fmap homeDirectory $ getUserEntryForID =<< getRealUserID
    homeDirectory <$> (getUserEntryForID =<< getRealUserID)
    

Because monads are associative, you can also do it the other way around:

  1. Compose homeDirectory with getUserEntryForID. The latter is already a standard Kleisli arrow, and you can lift homeDirectory to a Kleisli too in order to use the Kleisli composition operator:

    pure . homeDirectory :: UserEntry -> IO String
    pure . homeDirectory <=< getUserEntryForID :: UserId -> IO String
    
  2. Again apply that whole thing to getRealUserID:

    (pure . homeDirectory <=< getUserEntryForID) =<< getRealUserID
    

In fact you could also turn getRealUserID into a Kleisli arrow as well, with a dummy () argument. This style is rather uncommon in Haskell, but it has the advantage that the associativity of Kleisli composition becomes obvious in the same way it is obvious for normal function composition:

pure . homeDirectory <=< getUserEntryForID <=< const getRealUserID $ ()

Upvotes: 6

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476493

You can use >>=, since Haskell eventually "desugars" the do block to this:

getUserHome :: IO String
getUserHome = getRealUserID >>= \usr_id -> homeDirectory <$> getUserEntryForID usr_id

this can be simplified with:

getUserHome :: IO String
getUserHome  = getRealUserID >>= fmap homeDirectory . getUserEntryForID

or:

getUserHome :: IO String
getUserHome  = getRealUserID >>= (homeDirectory <$>) . getUserEntryForID

Upvotes: 8

Related Questions