{-| This module implements \"ListT done right\" in terms of proxies.

    The 'RespondT' monad transformer is the 'ListT' monad transformer over the
    downstream output type.  Each 'respond' corresponds to an element of the
    list.  The monad bind operation non-deterministically selects one of the
    previous 'respond's as input.  The 'RespondT' Kleisli category corresponds
    to the \"respond\" category of the 'ListT' class.

    Symmetrically, the 'RequestT' monad transformer is the 'ListT' monad
    transformer over the upstream output type.  Each 'request' corresponds to an
    element of the list.  The monad bind operation non-deterministically selects
    one of the previous 'request's as input.  The 'RequestT' Kleisli category
    corresponds to the \"request\" category of the 'ListT' class.

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

{-# LANGUAGE KindSignatures #-}

module Control.Proxy.ListT (
    -- * Respond Monad Transformer
    RespondT(..),
    runRespondK,
    ProduceT,

    -- * Request Monad Transformer
    RequestT(..),
    runRequestK,
    CoProduceT,

    -- * ListT
    ListT(..),
    (\>\),
    (/>/),

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

    -- * Laws
    -- $laws
    ) 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 Control.Proxy.Class (Proxy(request, respond), return_P, (?>=), lift_P)
import Control.Proxy.Synonym (C)
import Data.Monoid (Monoid(mempty, mappend))

-- For documentation
import Control.Monad ((>=>), (<=<))

-- | 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, ListT p) => Functor (RespondT p a' a b' m) where
    fmap f p = RespondT (runRespondT p //> \a -> respond (f a))

instance (Monad m, ListT 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, ListT p) => Monad (RespondT p a' a b' m) where
    return a = RespondT (respond a)
    m >>= f  = RespondT (runRespondT m //> \a -> runRespondT (f a))

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

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

instance (Monad m, ListT 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, ListT p, Monoid b') => MonadPlus (RespondT p a' a b' m) where
    mzero = empty
    mplus = (<|>)

-- | Convert a 'RespondT' \'@K@\'leisli arrow into a proxy
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 #-}

-- | 'ProduceT' is isomorphic to \"ListT done right\"
type ProduceT p = RespondT p C () ()

-- | 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, ListT p) => Functor (RequestT p a b' b m) where
    fmap f p = RequestT (runRequestT p //< \a -> request (f a))

instance (Monad m, ListT 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, ListT p) => Monad (RequestT p a b' b m) where
    return a = RequestT (request a)
    m >>= f  = RequestT (runRequestT m //< \a -> runRequestT (f a))

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

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

instance (Monad m, ListT 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, ListT p, Monoid a) => MonadPlus (RequestT p a b' b m) where
    mzero = empty
    mplus = (<|>)

-- | Convert a 'RequestT' \'@K@\'leisli arrow into a proxy
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 #-}

-- | 'CoProduceT' is isomorphic to \"ListT done right\"
type CoProduceT p = RequestT p () () C

{- * I make educated guesses about which associativy is most efficient for each
     operator.
   * Keep 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
-}
infixr 8 /</, >\\
infixl 8 \>\, //<
infixl 8 \<\, //>
infixr 8 />/, <\\

-- | The two generalized \"ListT\" categories
class (Proxy p) => ListT p where
    {-| @f >\\\\ p@ replaces all 'request's in @p@ with @f@.

        Equivalent to to ('=<<') for 'RequestT'

        Point-ful version of ('\>\')
    -}
    (>\\)
        :: (Monad m)
        => (b' -> p a' a x' x m b)
        ->        p b' b x' x m c
        ->        p a' a x' x m c

    {-| @p \/\/> f@ replaces all 'respond's in @p@ with @f@.

        Equivalent to ('>>=') for 'RespondT'

        Point-ful version of ('/>/')
    -}
    (//>)
        :: (Monad m)
        =>       p x' x b' b m a'
        -> (b -> p x' x c' c m b')
        ->       p x' x c' c m a'

{-| @f \\>\\ g@ replaces all 'request's in 'g' with 'f'.

    Equivalent to ('<=<') for 'RequestT'

    Point-free version of ('>\\')
-}
(\>\)
    :: (Monad m, ListT 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 (\>\) #-}

{-| @f \/>\/ g@ replaces all 'respond's in 'f' with 'g'.

    Equivalent to ('>=>') for 'RespondT'

    Point-free version of ('//>')
-}
(/>/)
    :: (Monad m, ListT 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, ListT 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, ListT 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, ListT 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, ListT 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 (<\\) #-}

{- $laws
    The 'ListT' class defines the ability to:
    
    * Replace existing 'request' commands using ('\>\')

    * Replace existing 'respond' commands using ('/>/')
    
    Laws:

    * ('/>/') and 'respond' form a ListT Kleisli category:

> return = respond
>
> (>=>) = (//>)

    * ('\>\') and 'request' also form a ListT Kleisli category:

> return = request
>
> (>>=) = (//<)

    Additionally, ('\>\') and ('/>/') both define functors between Proxy Kleisli
    categories:

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

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