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
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
First always consider the types:
getRealUserID :: IO UserId
getUserEntryForID :: UserId -> IO UserEntry
homeDirectory :: UserEntry -> String
Now you can:
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
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:
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
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 $ ()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With