Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use bind with nested monads?

Tags:

haskell

monads

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?

like image 353
madjar Avatar asked Jan 29 '15 12:01

madjar


2 Answers

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
like image 166
Sibi Avatar answered Nov 11 '22 00:11

Sibi


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
like image 7
user3237465 Avatar answered Nov 11 '22 01:11

user3237465