Reputation: 715
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
Reputation: 120711
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 $ ()
Upvotes: 6
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