I have two functions, one that tries to get a token from a webservice and may fail, and one that tries to use this token to get the username and may fail.
getToken :: IO (Maybe Token)
getUsername :: Token -> IO (Maybe String)
I would like to take the result of getToken and feed it to getUsername
. If there was only IO
or Maybe
, I could simply use bind, but since there are down nested monads, I can't. How can I write something equivalent to getToken >>= getUsername :: IO (Maybe String)
?
More generally, what function has type m1 m2 a -> (a -> m1 m2 b) -> m1 m2 b
?
Bonus question: how would I do that using the do notation in an IO
context?
I have defined a function useToken
showing your use case:
type Token = String
getToken :: IO (Maybe Token)
getToken = undefined
getUsername :: Token -> IO (Maybe String)
getUsername = undefined
useToken :: IO (Maybe String)
useToken = do
token <- getToken
case token of
Just x -> getUsername x
Nothing -> return Nothing
If you don't want to use do
notation, then you can use:
useToken2 :: IO (Maybe String)
useToken2 = getToken >>= \token -> maybe (return Nothing) getUsername token
Or using monad transformers, your code will become simpler:
import Control.Monad.Trans.Maybe
type Token = String
getToken :: MaybeT IO Token
getToken = undefined
getUsername :: Token -> MaybeT IO String
getUsername = undefined
useToken :: MaybeT IO String
useToken = do
token <- getToken
getUsername token
Note that, you can also directly lift IO operations inside the monad transformer. As @Robedino points out, now the code will be more concise without do notation:
useToken :: MaybeT IO String
useToken = getToken >>= getUsername
As people in the comments suggest, you should just use monad transformers.
However you can avoid this in your case. Monads do not commute in general, so you can't write a function with this signature
bind' :: (Monad m, Monad n) => m (n a) -> (a -> m (n b)) -> m (n b)
But all is ok, if the inner monad is an instance of the Traversable
class:
import Data.Traversable as T
import Control.Monad
joinT :: (Monad m, Traversable t, Monad t) => m (t (m (t a))) -> m (t a)
joinT = (>>= liftM join . T.sequence)
liftMM :: (Monad m, Monad n) => (a -> b) -> m (n a) -> m (n b)
liftMM = liftM . liftM
bindT :: (Monad m, Traversable t, Monad t) => m (t a) -> (a -> m (t b)) -> m (t b)
bindT x f = joinT (liftMM f x)
and the Maybe
monad is; hence
type Token = String
getToken :: IO (Maybe Token)
getToken = undefined
getUsername :: Token -> IO (Maybe String)
getUsername = undefined
useToken :: IO (Maybe String)
useToken = getToken `bindT` getUsername
Also, with the {-# LANGUAGE RebindableSyntax #-}
you can write
(>>=) = bindT
useToken :: IO (Maybe String)
useToken = do
x <- getToken
getUsername x
Update
With the type-level compose
newtype (f :. g) a = Nested { runNested :: f (g a) }
you can define a monad instance for nested monads:
instance (Monad m, Traversable t, Monad t) => Monad (m :. t) where
return = Nested . return . return
x >>= f = Nested $ runNested x `bindT` (runNested . f)
Your example then is
type Token = String
getToken :: IO (Maybe Token)
getToken = undefined
getUsername :: Token -> IO (Maybe String)
getUsername = undefined
useToken :: IO (Maybe String)
useToken = runNested $ Nested getToken >>= Nested . getUsername
Or like you would do with the MaybeT
transformer:
type Nested = (:.)
type Token = String
getToken :: Nested IO Maybe Token
getToken = undefined
getUsername :: Token -> Nested IO Maybe String
getUsername = undefined
useToken :: Nested IO Maybe String
useToken = getToken >>= getUsername
runUseToken :: IO (Maybe String)
runUseToken = runNested useToken
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