{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UndecidableInstances #-} {- | Module : Control.Monad.Request.Lazy Copyright : (c) Tom Hulihan 2014, License : MIT Maintainer : hulihan.tom159@gmail.com Stability : experimental Portability : non-portable (multi-parameter type classes) [Computation type:] Compuations that send requests and handle responses [Binding strategy:] Response callbacks are composed with the binding function [Useful for:] Implementation-agnostic requests (i.e. making real requests versus mocking), adding middlewares. [Example type:] @'Request' String String a@ The Request monad -} module Control.Monad.Request.Lazy ( -- * MonadRequest MonadRequest(..) -- * Request , Request , request , runRequest , mapRequest , mapResponse -- * RequestT , RequestT , requestT , runRequestT , mapRequestT , mapResponseT ) where import Control.Applicative import Control.Monad import Control.Monad.IO.Class import Control.Monad.Request.Class import Control.Monad.Trans.Class import Control.Monad.Error.Class import Control.Monad.RWS.Class import Data.Monoid import Data.Functor.Identity -------------------------------------------------------------------------------- -- 'Request' and its associated functions -- | A Request monad, parameterized by the request type, @r@, and response type, -- @r'@. -- together. type Request r r' = RequestT r r' Identity -- | Turn a request and response callback into a monadic computation. request :: r -- ^ The request -> (r' -> a) -- ^ The response callback -> Request r r' a -- ^ The resulting computation request r g = requestT r (Identity . g) -- | Evaluate a @'Request' r r\' a@ action. runRequest :: Request r r' a -- ^ The computation to run -> (r -> r') -- ^ A function that turns requests into responses -> a -- ^ The final result of the computation runRequest act f = runIdentity $ runRequestT act (Identity . f) -- | Given a @x -> r@, transform a computation that sends requests of type @x@ -- into one that sends requests of type @r@. mapRequest :: (x -> r) -- ^ The middleware function -> Request x r' a -- ^ The computation which sends @x@ -> Request r r' a -- ^ The computation which sends @r@ mapRequest f = mapRequestT (return . f) -- | Given a mapping from @r\' -> x@, transform a computation handles responses -- of type @x@ to one that handles responses of type @r'@. mapResponse :: (r' -> x) -- ^ The middleware function -> Request r x a -- ^ The computation which handles @x@ -> Request r r' a -- ^ The computation which handles @r'@ mapResponse f = mapResponseT (return . f) -------------------------------------------------------------------------------- -- 'RequestT' and its associated functions -- | A request monad, parameterized by the request type, @r@, response type, -- @r'@, and inner monad, @m@. data RequestT r r' m a = Pure a | Request r (r' -> RequestT r r' m a) | Lift (m (RequestT r r' m a)) -- | This function takes a request and monadic response handler to produce a -- @'RequestT' r r\' m a@. requestT :: Monad m => r -- ^ The request -> (r' -> m a) -- ^ The response callback -> RequestT r r' m a -- ^ The resulting computation requestT r g = Request r (Lift . liftM Pure . g) -- | Given a @'RequestT' r r\' m a@ and a mapping from requests to responses, -- return a monadic computation which produces @a@. runRequestT :: Monad m => RequestT r r' m a -- ^ The computation to run -> (r -> m r') -- ^ The request function -> m a -- ^ The resulting computation runRequestT m req = let go (Pure a) = return a go (Request r g) = req r >>= go . g go (Lift act) = act >>= go in go m -- | Turn a computation that requests @x@ into a computation that requests @r@. mapRequestT :: Monad m => (x -> RequestT r r' m r) -- ^ The middleware -> RequestT x r' m a -- ^ The @x@-requester -> RequestT r r' m a -- ^ The @r@-requester mapRequestT f = let go (Pure a) = Pure a go (Request x g) = f x >>= flip Request (go . g) go (Lift act) = Lift (liftM go act) in go -- | Turn a computation that handles @x@ into a computation that handles @r'@. mapResponseT :: Monad m => (r' -> RequestT r r' m x) -- ^ The middleware -> RequestT r x m a -- ^ The @x@-handler -> RequestT r r' m a -- ^ The @r'@-handler mapResponseT f = let go (Pure a) = Pure a go (Request r g) = Request r (go . g <=< f) go (Lift act) = Lift (liftM go act) in go -------------------------------------------------------------------------------- -- Type class instances from base. instance Alternative m => Alternative (RequestT r r' m) where empty = Lift empty (<|>) = let go (Pure a) _ = Pure a go (Request r g) x = Request r (flip go x . g) go (Lift act) x = Lift (fmap (flip go x) act) in go instance Applicative m => Applicative (RequestT r r' m) where pure = Pure (<*>) = let go (Pure f) (Pure a) = Pure (f a) go (Request r g) x = Request r (flip go x . g) go (Lift act) x = Lift (fmap (flip go x) act) go x (Request r g) = Request r (go x . g) go x (Lift act) = Lift (fmap (go x) act) in go instance Functor m => Functor (RequestT r r' m) where fmap f = let go (Pure a) = Pure (f a) go (Request r g) = Request r (go . g) go (Lift act) = Lift (fmap go act) in go instance Monad m => Monad (RequestT r r' m) where return = Pure fail = Lift . fail (>>=) m f = let go (Pure a) = f a go (Request r g) = Request r (go . g) go (Lift act) = Lift (liftM go act) in go m instance MonadPlus m => MonadPlus (RequestT r r' m) where mzero = Lift mzero mplus = let go (Pure a) _ = Pure a go (Request r g) x = Request r (flip go x . g) go (Lift act) x = Lift (liftM (flip go x) act) in go -------------------------------------------------------------------------------- -- Type class instances from this library. instance Monad m => MonadRequest r r' (RequestT r r' m) where send = flip Request Pure -------------------------------------------------------------------------------- -- Type class instances from transformers. instance MonadIO m => MonadIO (RequestT r r' m) where liftIO = lift . liftIO instance MonadTrans (RequestT r r') where lift = Lift . liftM Pure -------------------------------------------------------------------------------- -- Type class instances from mtl. instance MonadError e m => MonadError e (RequestT r r' m) where throwError = Lift . throwError catchError m h = let go (Pure a) = Pure a go (Request r g) = Request r (go . g) go (Lift act) = Lift (catchError act (return . h)) in go m instance MonadReader x m => MonadReader x (RequestT r r' m) where ask = lift ask local f = let go (Pure a) = Pure a go (Request r g) = Request r (go . g) go (Lift act) = Lift (liftM go (local f act)) in go instance (Monoid w, MonadReader r m, MonadWriter w m, MonadState s m) => MonadRWS r w s (RequestT r r' m) instance MonadState s m => MonadState s (RequestT r r' m) where get = lift get put = lift . put instance (Monoid w, MonadWriter w m) => MonadWriter w (RequestT r r' m) where writer = lift . writer tell = lift . tell listen = let go acc (Pure a) = Pure (a, acc) go acc (Request r g) = Request r (go acc . g) go acc (Lift act) = Lift $ do ~(m, w) <- listen act return (go (acc `mappend` w) m) in go mempty pass m = listen m >>= \ ~(~(a, f), w) -> writer (a, f w)