问题
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?
回答1:
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
回答2:
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
来源:https://stackoverflow.com/questions/28214913/how-to-use-bind-with-nested-monads