The client and server share the same state, which is sometimes not what we
want. We can easily solve this by running each Proxy with its own local
state by changing the order of the Proxy and StateT monad transformers:

Unfortunately, we can't modify server2 to catchT that error because we
cannot access the inner EitherT monad transformer until we run the
Session. We'd really prefer to place the EitherT monad transformer
outside the Proxy monad transformer so that we can catch and handle
errors locally within a Proxy without disturbing other proxies:

Proxy Transformers

We need some way to layer monad transformers outside the proxy type
without interfering with Proxy composition. To do this, we overload
Proxy composition using the Channel type class from
Control.Proxy.Class:

... but we would also like our monad transformers layered outside the
Proxy type to also implement the Channel class so that we could compose
them directly without unwrapping. Unfortunately, these monad transformers
do not fit the signature of the Channel class.

Fortunately, the Control.Proxy.Trans hierarchy provides several common
monad transformers which have been upgraded to fit the Channel type class.
I call these "proxy transformers".

This is why I call these "proxy transformers" and not monad transformers.
They all take some Proxy-like type that implements Channel and transform
it into a new Proxy-like type that also implements Channel. For
example, StateP implement the following instance:

instance (Channel p) => Channel (StateP s p) where ...

All proxy transformers guarantee that if the base proxy implements the
Channel type class, then the transformed proxy also implements the
Channel type class. This means that you can build a proxy transformer
stack, just like you might build a monad transformer stack.

Unfortunately, in order to use proxy transformers, you must expand out the
Client and Server type synonyms, which are not compatible with proxy
transformers. Sorry! This is why there are no Server or Client type
synonyms in the types of our new client and server and I had to write out
all the inputs and outputs.

Notice how the outermost lift statements in our client and server have
changed to liftP. liftP replaces lift for proxy transformers, and it
lifts any action in the base proxy to an action in the transformed proxy.
In the previous example, the base proxy was Proxy and the transformed
proxy was StateP s Proxy, so liftPs type got specialized to:

liftP :: Proxy a' a b' b m r -> StateP s Proxy a' a b' b m r

The ProxyTrans class defines liftP, and all proxy transformers implement
the ProxyTrans class. Since proxies are still monads, liftP must
behave just like lift and obey the monad transformer laws:

But, unlike lift, liftP obeys one extra set of laws that guarantee it
also lifts composition sensibly:

(liftP .) idT = idT
(liftP .) (f >-> g) = (liftP .) f >-> (liftP .) g

In fact, this (liftP .) pattern is so ubiquitous, that the ProxyTrans
class provides the additional mapP method for convenience:

mapP = (liftP .)

Proxy transformers automatically derive how to lift composition correctly
and also guarantee that the derived composition obeys the category laws if
the base composition obeyed the category laws. Since Proxy composition
obeys the category laws, any proxy transformer stack built on top of it
automatically derives a composition operation that is correct by
construction.

Compatibility

Proxy transformers do more than just lift composition. They automatically
promote proxies written in the base monad. For example, what if I wanted to
use the takeB_ proxy from Control.Proxy.Prelude.Base to cap the number
of results? I can't compose it directly because it uses the Proxy type:

takeB_ :: (Monad m) => Int -> a' -> Proxy a' a a' a m ()

... whereas client6 and server6 use EitherP String Proxy. However,
this doesn't matter because we can automatically lift takeB_ to be
compatible with them using mapP:

mapP promotes any proxy written using the base proxy type to automatically
be compatible with proxies written using the extended proxy type. This
means you can safely write utility proxies using the smallest feature set
they require and promote them as necessary to work with more extended
feature sets. This ensures that any proxies you write always remain
forwards-compatible as people write new extensions.

Proxy Transformer Stacks

You can stack proxy transformers to combine their effects, such as in the
following example, which combines everything we've used so far:

But that's still not the full story! For calls to the base monad (i.e. IO
in this case), you don't need to precede them with all those liftPs.
Every proxy transformer also correctly derives MonadTrans, so you can dig
straight to the base monad by just calling lift at the outer-most level:

Note that request and respond are not automatically liftable, because of
technical limitations with Haskell type classes. When I resolve these
issues they will also be automatically promoted by proxy transformers. For
now, you must lift them manually using liftP:

request = (liftP .) request
respond = (liftP .) respond

The left request and respond in the above equations are what the lifted
definitions would be for each proxy transformer if Haskell's type class
system didn't get in my way.