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:
apply
getRealUserID
withgetUserEntryForID
. 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 withfmap
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
withgetUserEntryForID
. The latter is already a standard Kleisli arrow, and you can lifthomeDirectory
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 $ ()