Home > Net >  Compose IO Operations
Compose IO Operations

Time:08-29

Hi im 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 im 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 theres an alternative operator for the . that respects the Monad ..? But in all my searching, i havent found it.


I tried <$> but that doesnt 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

Thanks for your help^^

CodePudding user response:

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

CodePudding user response:

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 $ ()
  • Related