pipes-3.1.0: Compositional pipelines

Safe HaskellSafe-Inferred

Control.Proxy.Class

Contents

Description

The Proxy class defines the library's core API. Everything else in this library builds exclusively on top of the Proxy type class so that all proxy implementations and extensions can share the same standard library.

Several of these type classes duplicate methods from familiar type-classes (such as (?>=) duplicating (>>=)). You do NOT need to use these duplicate methods. Instead, read the "Polymorphic proxies" section below which explains their purpose and how they help clean up type signatures.

Synopsis

Core proxy class

class Proxy p whereSource

The core API for the pipes library

You should only use request, respond, and (>->)

I only provide (>~>) for theoretical symmetry, and the remaining methods just implement internal type class plumbing.

Methods

request :: Monad m => a' -> p a' a b' b m aSource

request input from upstream, passing an argument with the request

request a' passes a' as a parameter to upstream that upstream may use to decide what response to return. request binds the upstream's response of type a to its own return value.

respond :: Monad m => b -> p a' a b' b m b'Source

respond with an output for downstream and bind downstream's next request

respond b satisfies a downstream request by supplying the value b. respond blocks until downstream requests a new value and binds the argument of type b' from the next request as its return value.

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

Compose two proxies blocked on a respond, generating a new proxy blocked on a respond

Begins from the downstream end and satisfies every request with a respond

(>~>) :: Monad m => (a -> p a' a b' b m r) -> (b -> p b' b c' c m r) -> a -> p a' a c' c m rSource

Compose two proxies blocked on a request, generating a new proxy blocked on a request

Begins from the upstream end and satisfies every respond with a request

return_P :: Monad m => r -> p a' a b' b m rSource

return_P is identical to return, except with a more polymorphic constraint.

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

(?>=) is identical to (>>=), except with a more polymorphic constraint.

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

lift_P is identical to lift, except with a more polymorphic constraint.

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

hoist_P is identical to hoist, except with a more polymorphic constraint.

Instances

Proxy ProxyFast 
Proxy ProxyCorrect 
Proxy p => Proxy (IdentityP p) 
Proxy p => Proxy (MaybeP p) 
Proxy p => Proxy (EitherP e p) 
Proxy p => Proxy (ReaderP i p) 
Proxy p => Proxy (StateP s p) 
Proxy p => Proxy (WriterP w p) 

idT :: (Monad m, Proxy p) => a' -> p a' a a' a m rSource

idT forwards requests followed by responses

 idT = request >=> respond >=> idT

coidT :: (Monad m, Proxy p) => a -> p a' a a' a m rSource

coidT forwards responses followed by requests

 coidT = respond >=> request >=> coidT

(<-<) :: (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 rSource

Compose two proxies blocked on a respond, generating a new proxy blocked on a respond

Begins from the downstream end and satisfies every request with a respond

(<~<) :: (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 rSource

Compose two proxies blocked on a request, generating a new proxy blocked on a request

Begins from the upstream end and satisfies every respond with a request

You don't need to use this. I include it only for symmetry.

request/respond substitution

class Interact p whereSource

Two extra Proxy categories of theoretical interest

Methods

(\>\) :: Monad m => (b' -> p a' a x' x m b) -> (c' -> p b' b x' x m c) -> c' -> p a' a x' x m cSource

f \>\ g replaces all requests in g with f.

(/>/) :: Monad m => (a -> p x' x b' b m a') -> (b -> p x' x c' c m b') -> a -> p x' x c' c m a'Source

f />/ g replaces all responds in f with g.

(/</) :: (Monad m, Interact p) => (c' -> p b' b x' x m c) -> (b' -> p a' a x' x m b) -> c' -> p a' a x' x m cSource

f /</ g replaces all requests in f with g.

(\<\) :: (Monad m, Interact 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'Source

f \<\ g replaces all responds in g with f.

Laws

The Proxy class defines an interface to all core proxy capabilities that all proxy-like types must implement.

First, all proxies must support a bidirectional flow of information, defined by:

Intuitively, both p1 >-> p2 and p1 >~> p2 pair each request in p2 with a respond in p1. (>->) accepts proxies blocked on respond and begins from the downstream end, whereas (>~>) accepts proxies blocked on request and begins from the upstream end.

Second, all proxies are monads, defined by:

These must satify the monad laws using (>>=) = (?>=) and return = return_P.

Third, all proxies are monad transformers, defined by:

This must satisfy the monad transformer laws, using lift = lift_P.

Fourth, all proxies are functors in the category of monads, defined by:

This must satisfy the functor laws, using hoist = hoist_P.

All Proxy instances must satisfy these additional laws:

  • (>->) and idT form a category:
 Define: idT = request >=> respond >=> idT

 idT >-> p = p

 p >-> idT = p

 (p1 >-> p2) >-> p3 = p1 >-> (p2 >-> p3)
 Define: coidT = respond >=> request >=> coidT

 coidT >~> p = p

 p >~> coidT = p

 (p1 >~> p2) >~> p3 = p1 >~> (p2 >~> p3)

Also, all proxies must satisfy the following Proxy laws:

 -- Define: liftK = (lift .)

 p1 >-> liftK f = liftK f

 p1 >-> (liftK f >=> respond >=> p2) = liftK f >=> respond >=> (p1 >-> p2)

 (liftK g >=> respond >=> p1) >-> (liftK f >=> request >=> liftK h >=> p2)
     = liftK (f >=> g >=> h) >=> (p1 >-> p2)

 (liftK g >=> request >=> p1) >-> (liftK f >=> request >=> p2)
     = liftK (f >=> g) >=> request >=> (p1 >~> p2)

 liftK f >~> p2 = liftK f

 (liftK f >=> request >=> p1) >~> p2 = liftK f >=> request >=> (p1 >~> p2)

 (liftK f >=> respond >=> liftK h >=> p1) >~> (liftK g >=> request >=> p2)
     = liftK (f >=> g >=> h) >=> (p1 >~> p2)

 (liftK f >=> respond >=> p1) >~> (liftK g >=> respond >=> p2)
     = liftK (f >=> g) >=> (p1 >-> p2)

The Interact class exists primarily for theoretical interest and to justify some of the functor laws for the ProxyTrans type class. You will probably never use it.

The Interact class defines the ability to:

Laws:

 request \>\ f = f

 f \>\ request = f

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

 f />/ respond = f

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

Additionally, (\>\) and (/>/) distribute in one direction over Kleisli composition:

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

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

 return />/ a = return

Polymorphic proxies

Many of these type classes contain methods which copy 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 (MonadTransP 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) => ...

These type classes exist solely for internal plumbing and you should never directly use the duplicate methods from them. 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

class Proxy p => MonadPlusP p whereSource

The (MonadPlusP p) constraint is equivalent to the following constraint:

 (forall a' a b' b m . (Monad m) => MonadPlus (p a' a b' b m)) => ...

Methods

mzero_P :: Monad m => p a' a b' b m rSource

mplus_P :: Monad m => p a' a b' b m r -> p a' a b' b m r -> p a' a b' b m rSource

Instances

class Proxy p => MonadIOP p whereSource

The (MonadIOP p) constraint is equivalent to the following constraint:

 (forall a' a b' b m . (MonadIO m) => MonadIO (p a' a b' b m)) => ...

Methods

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