-- | This module defines the theoretical framework underpinning this library

{-# LANGUAGE Rank2Types, KindSignatures #-}

module Control.Proxy.Class (
    -- * The Proxy Class
    Proxy(..),

    -- * Composition operators
    (>->),
    (>~>),
    (\>\),
    (/>/),

    -- ** Flipped operators
    (<-<),
    (<~<),
    (/</),
    (\<\),
    (<<-),
    (~<<),
    (//<),
    (<\\),

    -- * ListT Monad Transformers
    -- $listT
    RespondT(..),
    RequestT(..),

    -- * Synonyms
    C,
    Pipe,
    Producer,
    Consumer,
    CoPipe,
    CoProducer,
    CoConsumer,
    Client,
    Server,
    Session,
    ProduceT,
    CoProduceT,

    -- * Laws
    -- $laws

    -- * Polymorphic proxies
    -- $poly
    ProxyInternal(..),
    MonadPlusP(..),

    -- * Deprecated
    -- $deprecate
    idT,
    coidT,
    ListT,
    runRespondK,
    runRequestK
    ) where

import Control.Applicative (Applicative(pure, (<*>)), Alternative(empty, (<|>)))
import Control.Monad (MonadPlus(mzero, mplus))
import Control.Monad.IO.Class (MonadIO(liftIO))
import Control.Monad.Trans.Class (MonadTrans(lift))
import Data.Monoid (Monoid(mempty, mappend))

{- * Keep proxy composition lower in precedence than function composition, which
     is 9 at the time of of this comment, so that users can write things like:

> lift . k >-> p
>
> hoist f . k >-> p

   * Keep the priorities different so that users can mix composition operators
     like:

> up \>\ p />/ dn
>
> up >~> p >-> dn

   * Keep 'request' and 'respond' composition lower in precedence than 'pull'
     and 'push' composition, so that users can do:

> read \>\ pull >-> writer

   * I arbitrarily choose a lower priority for downstream operators so that lazy
     pull-based computations need not evaluate upstream stages unless absolutely
     necessary.
-}
infixr 5 <-<, ->>
infixl 5 >->, <<-
infixr 6 >~>, ~<<
infixl 6 <~<, >>~
infixl 7 \<\, //>
infixr 7 />/, <\\
infixr 8 /</, >\\
infixl 8 \>\, //<
infixl 1 ?>=  -- This should match the fixity of >>=

{-| The 'Proxy' class defines a 'Monad' that intersects four streaming
    categories:

    * The \"request\" category: 'request' and ('\>\')

    * The \"respond\" category: 'respond' and ('/>/')

    * The \"pull\" category: 'pull' and ('>->')

    * The \"push\" category: 'push' and ('>~>')

    This class requires the \"point-ful\" version of each category's composition
    operator for efficiency.

    Minimal definition:

    * 'request'

    * 'respond'

    * ('->>')

    * ('>>~')

    * ('>\\')

    * ('//>')

    * 'turn'
-}
class (ProxyInternal p) => Proxy p where
    {-| 'request' sends a value of type @a'@ upstream and receives a value of
        type @a@.
    -}
    request :: (Monad m) => a' -> p a' a b' b m a

    -- | @(f >\\\\ p)@ replaces each 'request' in @p@ with @f@.
    (>\\)
        :: (Monad m)
        => (b' -> p a' a x' x m b)
        ->        p b' b x' x m c
        ->        p a' a x' x m c

    {-| 'respond' sends a value of type @b@ downstream and receives a value of
        type @b'@.
    -}
    respond :: (Monad m) => b -> p a' a b' b m b'

    -- | @(p \/\/> f)@ replaces each 'respond' in @p@ with @f@.
    (//>)
        :: (Monad m)
        =>       p x' x b' b m a'
        -> (b -> p x' x c' c m b')
        ->       p x' x c' c m a'

    -- | @pull = request >=> respond >=> pull@
    pull :: (Monad m, Proxy p) => a' -> p a' a a' a m r
    pull = go where
      go a' =
        request a' ?>= \a   ->
        respond a  ?>= \a'2 ->
        go a'2
    {- DO NOT replace 'go' with 'push' or ghc-7.4.2 will not terminate while
       compiling `pipes` -}

    -- | @(f ->> p)@ pairs each 'request' in @p@ with a 'respond' in @f@.
    (->>)
        :: (Monad m)
        => (b'  -> p a' a b' b m r)
        ->         p b' b c' c m r
        ->         p a' a c' c m r

    -- | @push = respond >=> request >=> push@
    push :: (Monad m, Proxy p) => a -> p a' a a' a m r
    push = go where
      go a =
        respond a  ?>= \a' ->
        request a' ?>= \a2 ->
        go a2
    {- DO NOT replace 'go' with 'push' or ghc-7.4.2 will not terminate while
       compiling `pipes` -}

    -- | @(p >>~ f)@ pairs each 'respond' in @p@ with a 'request' in @f@.
    (>>~)
        :: (Monad m)
        =>        p a' a b' b m r
        -> (b  -> p b' b c' c m r)
        ->        p a' a c' c m r

    -- | 'turn' swaps 'request's and 'respond's
    turn :: (Monad m) => p a' a b' b m r -> p b b' a a' m r

{-| \"pull\" composition

> (f >-> g) x = f ->> g x

    Compose two proxies blocked on a 'respond', generating a new proxy blocked
    on a 'respond'
-}
(>->)
    :: (Monad m, Proxy p)
    => (b'  -> p a' a b' b m r)
    -> (c'_ -> p b' b c' c m r)
    -> (c'_ -> p a' a c' c m r)
f >-> g = \c' -> f ->> g c'
{-# INLINABLE (>->) #-}

{-| \"push\" composition

> (f >~> g) x = f x >>~ g

    Compose two proxies blocked on a 'request', generating a new proxy blocked
    on a 'request'
-}
(>~>)
    :: (Monad m, Proxy p)
    => (a_ -> p a' a b' b m r)
    -> (b  -> p b' b c' c m r)
    -> (a_ -> p a' a c' c m r)
k1 >~> k2 = \a -> k1 a >>~ k2
{-# INLINABLE (>~>) #-}

{-| \"request\" composition

> (f \>\ g) x = f >\\ g x

    Compose two folds, generating a new fold
-}
(\>\)
    :: (Monad m, Proxy p)
    => (b' -> p a' a x' x m b)
    -> (c' -> p b' b x' x m c)
    -> (c' -> p a' a x' x m c)
f \>\ g = \c' -> f >\\ g c'
{-# INLINABLE (\>\) #-}

{-| \"respond\" composition

> (f />/ g) x = f x //> g

    Compose two unfolds, generating a new unfold
-}
(/>/)
    :: (Monad m, Proxy p)
    => (a -> p x' x b' b m a')
    -> (b -> p x' x c' c m b')
    -> (a -> p x' x c' c m a')
f />/ g = \a -> f a //> g
{-# INLINABLE (/>/) #-}

-- | Equivalent to ('>->') with the arguments flipped
(<-<)
    :: (Monad m, Proxy p)
    => (c' -> p b' b c' c m r)
    -> (b' -> p a' a b' b m r)
    -> (c' -> p a' a c' c m r)
p1 <-< p2 = p2 >-> p1
{-# INLINABLE (<-<) #-}

-- | Equivalent to ('>~>') with the arguments flipped
(<~<)
    :: (Monad m, Proxy p)
    => (b -> p b' b c' c m r)
    -> (a -> p a' a b' b m r)
    -> (a -> p a' a c' c m r)
p1 <~< p2 = p2 >~> p1
{-# INLINABLE (<~<) #-}

-- | Equivalent to ('\>\') with the arguments flipped
(/</)
    :: (Monad m, Proxy p)
    => (c' -> p b' b x' x m c)
    -> (b' -> p a' a x' x m b)
    -> (c' -> p a' a x' x m c)
p1 /</ p2 = p2 \>\ p1
{-# INLINABLE (/</) #-}

-- | Equivalent to ('/>/') with the arguments flipped
(\<\)
    :: (Monad m, Proxy p)
    => (b -> p x' x c' c m b')
    -> (a -> p x' x b' b m a')
    -> (a -> p x' x c' c m a')
p1 \<\ p2 = p2 />/ p1
{-# INLINABLE (\<\) #-}

-- | Equivalent to ('->>') with the arguments flipped
(<<-)
    :: (Monad m, Proxy p)
    =>         p b' b c' c m r
    -> (b'  -> p a' a b' b m r)
    ->         p a' a c' c m r
k <<- p = p ->> k
{-# INLINABLE (<<-) #-}

-- | Equivalent to ('>>~') with the arguments flipped
(~<<)
    :: (Monad m, Proxy p)
    => (b  -> p b' b c' c m r)
    ->        p a' a b' b m r
    ->        p a' a c' c m r
k ~<< p = p >>~ k
{-# INLINABLE (~<<) #-}

-- | Equivalent to ('>\\') with the arguments flipped
(//<)
    :: (Monad m, Proxy p)
    =>        p b' b x' x m c
    -> (b' -> p a' a x' x m b)
    ->        p a' a x' x m c
p //< f = f >\\ p
{-# INLINABLE (//<) #-}

-- | Equivalent to ('//>') with the arguments flipped
(<\\)
    :: (Monad m, Proxy p)
    => (b -> p x' x c' c m b')
    ->       p x' x b' b m a'
    ->       p x' x c' c m a'
f <\\ p = p //> f
{-# INLINABLE (<\\) #-}

-- | A monad transformer over a proxy's downstream output
newtype RespondT (p :: * -> * -> * -> * -> (* -> *) -> * -> *) a' a b' m b =
    RespondT { runRespondT :: p a' a b' b m b' }

instance (Monad m, Proxy p) => Functor (RespondT p a' a b' m) where
    fmap f p = RespondT (runRespondT p //> \a -> respond (f a))

instance (Monad m, Proxy p) => Applicative (RespondT p a' a b' m) where
    pure a = RespondT (respond a)
    mf <*> mx = RespondT (
        runRespondT mf //> \f ->
        runRespondT mx //> \x ->
        respond (f x) )

instance (Monad m, Proxy p) => Monad (RespondT p a' a b' m) where
    return a = RespondT (respond a)
    m >>= f  = RespondT (runRespondT m //> \a -> runRespondT (f a))

instance (Proxy p) => MonadTrans (RespondT p a' a b') where
    lift m = RespondT (lift_P m ?>= \a -> respond a)

instance (MonadIO m, Proxy p) => MonadIO (RespondT p a' a b' m) where
    liftIO m = lift (liftIO m)

instance (Monad m, Proxy p, Monoid b')
       => Alternative (RespondT p a' a b' m) where
    empty = RespondT (return_P mempty)
    p1 <|> p2 = RespondT (
        runRespondT p1 ?>= \r1 ->
        runRespondT p2 ?>= \r2 ->
        return_P (mappend r1 r2) )

instance (Monad m, Proxy p, Monoid b') => MonadPlus (RespondT p a' a b' m) where
    mzero = empty
    mplus = (<|>)

{- $listT
    The 'RespondT' monad transformer is equivalent to 'ListT' over the
    downstream output.  The 'RespondT' Kleisli category corresponds to the
    \"respond\" category.

    The 'RequestT' monad transformer is equivalent to 'ListT' over the upstream
    output.  The 'RequestT' Kleisli category corresponds to the \"request\"
    category.

    Unlike 'ListT' from @transformers@, these monad transformers are correct by
    construction and always satisfy the monad and monad transformer laws.
-}


-- | A monad transformer over a proxy's upstream output
newtype RequestT (p :: * -> * -> * -> * -> (* -> *) -> * -> *) a b' b m a' =
    RequestT { runRequestT :: p a' a b' b m a }

instance (Monad m, Proxy p) => Functor (RequestT p a b' b m) where
    fmap f p = RequestT (runRequestT p //< \a -> request (f a))

instance (Monad m, Proxy p) => Applicative (RequestT p a b' b m) where
    pure a = RequestT (request a)
    mf <*> mx = RequestT (
        runRequestT mf //< \f ->
        runRequestT mx //< \x ->
        request (f x) )

instance (Monad m, Proxy p) => Monad (RequestT p a b' b m) where
    return a = RequestT (request a)
    m >>= f  = RequestT (runRequestT m //< \a -> runRequestT (f a))

instance (Proxy p) => MonadTrans (RequestT p a' a b') where
    lift m = RequestT (lift_P m ?>= \a -> request a)

instance (MonadIO m, Proxy p) => MonadIO (RequestT p a b' b m) where
    liftIO m = lift (liftIO m)

instance (Monad m, Proxy p, Monoid a)
       => Alternative (RequestT p a b' b m) where
    empty = RequestT (return_P mempty)
    p1 <|> p2 = RequestT (
        runRequestT p1 ?>= \r1 ->
        runRequestT p2 ?>= \r2 ->
        return_P (mappend r1 r2) )

instance (Monad m, Proxy p, Monoid a) => MonadPlus (RequestT p a b' b m) where
    mzero = empty
    mplus = (<|>)

-- | The empty type, denoting a \'@C@\'losed end
data C = C -- Constructor not exported, but I include it to avoid EmptyDataDecls

-- | A unidirectional 'Proxy'.
type Pipe (p :: * -> * -> * -> * -> (* -> *) -> * -> *) a b = p () a () b

{-| A 'Pipe' that produces values

    'Producer's never 'request'.
-}
type Producer (p :: * -> * -> * -> * -> (* -> *) -> * -> *) b = p C () () b

{-| A 'Pipe' that consumes values

    'Consumer's never 'respond'.
-}
type Consumer (p :: * -> * -> * -> * -> (* -> *) -> * -> *) a = p () a () C

-- | A 'Pipe' where everything flows upstream
type CoPipe (p :: * -> * -> * -> * -> (* -> *) -> * -> *) a' b' = p a' () b' ()

{-| A 'CoPipe' that produces values flowing upstream

    'CoProducer's never 'respond'.
-}
type CoProducer (p :: * -> * -> * -> * -> (* -> *) -> * -> *) a' = p a' () () C

{-| A 'CoConsumer' that consumes values flowing upstream

    'CoConsumer's never 'request'.
-}
type CoConsumer (p :: * -> * -> * -> * -> (* -> *) -> * -> *) b' = p C () b' ()

{-| @Server b' b@ receives requests of type @b'@ and sends responses of type
    @b@.

    'Server's never 'request'.
-}
type Server (p :: * -> * -> * -> * -> (* -> *) -> * -> *) b' b = p C () b' b

{-| @Client a' a@ sends requests of type @a'@ and receives responses of
    type @a@.

    'Client's never 'respond'.
-}
type Client (p :: * -> * -> * -> * -> (* -> *) -> * -> *) a' a = p a' a () C

{-| A self-contained 'Session', ready to be run by 'runProxy'

    'Session's never 'request' or 'respond'.
-}
type Session (p :: * -> * -> * -> * -> (* -> *) -> * -> *) = p C () () C

-- | 'ProduceT' is 'ListT' over the downstream output
type ProduceT p = RespondT p C () ()

-- | 'CoProduceT' is 'ListT' over the upstream output
type CoProduceT p = RequestT p () () C

{- $laws
    First, all proxies sit at the intersection of five categories:

    * The Kleisli category (all proxies are monads)

> return >=> f = f
>
> f >=> return = f
>
> (f >=> g) >=> h = f >=> (g >=> h)

    * The \"request\" category

> request \>\ f = f
>
> f \>\ request = f
>
> (f \>\ g) \>\ h = f \>\ (g \>\ h)

    * The \"respond\" category

> respond />/ f = f
>
> f />/ respond = f
>
> (f />/ g) />/ h = f />/ (g />/ h)

    * The \"pull\" category

> pull >-> f = f
>
> f >-> pull = f
>
> (f >-> g) >-> h = (f >-> g) >-> h

    * The \"push\" category

> push >~> f = f
>
> f >~> push = f
>
> (f >~> g) >~> h = f >~> (g >~> h)

    Second, @(turn .)@ transforms each streaming category into its dual:

    * The \"request\" category

> turn . request = respond
>
> turn . (f \>\ g) = turn . f \<\ turn . g

    * The \"respond\" category

> turn . respond = request
>
> turn . (f />/ g) = turn . f /</ turn. g

    * The \"pull\" category

> turn . pull = push
>
> turn . (f >-> g) = turn . f <~< turn . g

    * The \"push\" category

> turn . push = pull
>
> turn . (f >~> g) = turn . f <-< turn . g

    Third, all proxies are monad transformers and must satisfy the monad
    transformer laws, using:

    * @lift = lift_P@

    Fourth, all proxies are functors in the category of monads and must satisfy
    the functor laws, using:

    *  @hoist = hoist_P@

    Fifth, ('\>\') and ('/>/') both define functors between Kleisli categories

> a \>\ (b >=> c) = (a \>\ b) >=> (a \>\ c)
>
> a \>\ return = return

> (b >=> c) />/ a = (b />/ a) >=> (c />/ a)
>
> return />/ a = return

    Sixth, all proxies must satisfy these additional 'Proxy' laws:

> p \>\ lift . f = lift . f
>
> p \>\ respond  = respond
>
> lift . f />/ p = lift . f
>
> request />/  p = request
>
> pull = request >=> respond >=> pull
>
> push = respond >=> request >=> push
>
> p1 >-> lift . f = lift . f
>
> p1 >-> (lift . f >=> respond >=> p2) = lift . f >=> respond >=> (p1 >-> p2)
>
> (lift . g >=> respond >=> p1) >-> (lift . f >=> request >=> lift . h >=> p2)
>     = lift . (f >=> g >=> h) >=> (p1 >-> p2)
>
> (lift . g >=> request >=> p1) >-> (lift . f >=> request >=> p2)
>     = lift . (f >=> g) >=> request >=> (p1 >~> p2)
>
> lift . f >~> p2 = lift . f
>
> (lift . f >=> request >=> p1) >~> p2 = lift . f >=> request >=> (p1 >~> p2)
>
> (lift . f >=> respond >=> lift . h >=> p1) >~> (lift . g >=> request >=> p2)
>     = lift . (f >=> g >=> h) >=> (p1 >~> p2)
>
> (lift . f >=> respond >=> p1) >~> (lift . g >=> respond >=> p2)
>     = lift . (f >=> g) >=> (p1 >-> p2)

-}

{- $poly
    The 'ProxyInternal' and 'MonadPlusP' type classes duplicate methods from
    more familiar type classes.  These duplicate methods serve two purposes.

    First, this library requires type class instances that would otherwise be
    impossible to define without providing higher-kinded constraints.  Rather
    than use the following illegal polymorphic constraint:

> instance (forall a' a b' b . MonadTrans (p a' a b' b)) => ...

    ... the instance can instead use the following Haskell98 constraint:

> instance (Proxy p) => ...

    Second, these type classes don't require the @FlexibleContexts@ extension
    to use and substantially clean up constraints in type signatures.  They
    convert messy constraints like this:

> p :: (MonadP (p a' a b' b m), MonadTrans (p a' a b' b)) => ...

      .. into cleaner and more general constraints like this:

> p :: (Proxy p) => ...

    'ProxyInternal' and 'MonadPlusP' exist solely for internal type class
    plumbing and I discourage you from using the methods in these classes
    unless you enjoy making your code less readable.  Instead, you can use all
    the original type classes as long as you embed your proxy code within at
    least one proxy transformer (or 'IdentityP' if don't use any transformers).
    The type-class machinery will then automatically convert the messier and
    less polymorphic constraints to the simpler and more general constraints.

    For example, consider the following almost-correct definition for @mapMD@
    (from "Control.Proxy.Prelude.Base"):

> import Control.Monad.Trans.Class
> import Control.Proxy
>
> mapMD f = foreverK $ \a' -> do
>     a <- request a'
>     b <- lift (f a)
>     respond b

    The compiler infers the following messy constraint:

> mapMD
>  :: (Monad m, Monad (p x a x b m), MonadTrans (p x a x b), Proxy p)
>  => (a -> m b) -> x -> p x a x b m r

    Instead, you can embed the code in the @IdentityP@ proxy transformer by
    wrapping it in 'runIdentityK':

> --        |difference|  
> mapMD f = runIdentityK $ foreverK $ \a' -> do
>     a <- request a'
>     b <- lift (f a)
>     respond b

    ... and now the compiler collapses all the constraints into the 'Proxy'
    constraint:

> mapMD :: (Monad m, Proxy p) => (a -> m b) -> x -> p x a x b m r

    You do not incur any performance penalty for writing polymorphic code or
    embedding it in 'IdentityP'.  This library employs several rewrite @RULES@
    which transform your polymorphic code into the equivalent type-specialized
    hand-tuned code.  These rewrite rules fire very robustly and they do not
    require any assistance on your part from compiler pragmas like @INLINE@,
    @NOINLINE@ or @SPECIALIZE@.

    If you nest proxies within proxies:

> example () = do
>     request ()
>     lift $ request ()
>     lift $ lift $ request ()

    ... then you can still keep the nice constraints using:

> example () = runIdentityP . hoist (runIdentityP . hoist runIdentityP) $ do
>     request ()
>     lift $ request ()
>     lift $ lift $ request ()

    You don't need to use 'runIdentityP' \/ 'runIdentityK' if you use any other
    proxy transformers (In fact you can't, it's a type error).  The following
    code example illustrates this, where the 'throw' command (from the 'EitherP'
    proxy transformer) suffices to guide the compiler to the cleaner type
    signature:

> import Control.Monad
> import Control.Proxy
> import qualified Control.Proxy.Trans.Either as E
>
> example :: (Monad m, Proxy p) => () -> Producer (EitherP String p) Char m ()
> example () = do
>     c <- request ()
>     when (c == ' ') $ E.throw "Error: received space"
>     respond c
-}

{-| The @(ProxyInternal p)@ constraint is (basically) equivalent to the
    following polymorphic constraint:

> (forall a' a b' b m . (Monad m)
>     => Monad      (p a' a b' b m)
>     ,  MonadTrans (p a' a b' b  )
>     ,  MFunctor   (p a' a b' b m)
>     ,  MonadIO    (p a' a b' b m)
>     ) => ...
-}
class ProxyInternal p where
    return_P :: (Monad m) => r -> p a' a b' b m r
    (?>=)
        :: (Monad m)
        => p a' a b' b m r -> (r -> p a' a b' b m r') -> p a' a b' b m r'

    lift_P :: (Monad m) => m r -> p a' a b' b m r

    hoist_P
        :: (Monad m)
        => (forall r . m r  -> n r) -> (p a' a b' b m r' -> p a' a b' b n r')

    liftIO_P :: (MonadIO m) => IO r -> p a' a b' b m r

    thread_P
        :: (Monad m)
        => p a' a b' b m r -> s -> p (a', s) (a, s) (b', s) (b, s) m (r, s)

{-| The @(MonadPlusP p)@ constraint is equivalent to the following polymorphic
    constraint:

> (forall a' a b' b m . (Monad m) => MonadPlus (p a' a b' b m)) => ...
-}
class (Proxy p) => MonadPlusP p where
    mzero_P :: (Monad m) => p a' a b' b m r
    mplus_P
        :: (Monad m) => p a' a b' b m r -> p a' a b' b m r -> p a' a b' b m r

{- $deprecate
    These will be removed in version @4.0.0@
-}

idT :: (Monad m, Proxy p) => a' -> p a' a a' a m r
idT = pull
{-# INLINABLE idT #-}
{-# DEPRECATED idT "Use 'pull' instead" #-}

coidT :: (Monad m, Proxy p) => a -> p a' a a' a m r
coidT = push
{-# INLINABLE coidT #-}
{-# DEPRECATED coidT "Use 'push' instead" #-}

class (Proxy p) => ListT p where
{-# DEPRECATED ListT "Use 'Proxy' instead" #-}

runRespondK :: (q -> RespondT p a' a b' m b) -> (q -> p a' a b' b m b')
runRespondK k q = runRespondT (k q)
{-# INLINABLE runRespondK #-}
{-# DEPRECATED runRespondK "Use '(runRespondT .)' instead" #-}

runRequestK :: (q -> RequestT p a b' b m a') -> (q -> p a' a b' b m a)
runRequestK k q = runRequestT (k q)
{-# INLINABLE runRequestK #-}
{-# DEPRECATED runRequestK "Use '(runRequestK .)' instead" #-}