`Sync` session
In some kinds of protocols (for example, relay networks) it may make sense for both sides of the transmission to be capable of sending to the other side. However, in our library it is only possible for one side to send to the other at any given time.
We either need a new session type that allows both sides to send a message at the same time, or a rethink of the design to perhaps anticipate two channels.
Design 1
In Sync<P, Q>, you can transition to P by sending a discriminant over the channel and transitioning to Latent<P>. After you receive a discriminant back, regardless of what you receive, you transition to P immediately. Latent will allow you to send but not to receive. The degenerate case, transitioning to Q, transitions you to Proposed<P, Q> and in the mean time sends a discriminant. If you're in this session you transition to either P or Q as instructed.
This allows only one party to begin sending immediately through P. However, transitioning to Q requires full negotiation.
This is insufficient for our needs but still interesting.
Design 2
Duplex<((), P), ((), Q)> is a session that allows two protocols to be interacted with concurrently. The Duplex holds the environment for both sessions. Either side can immediately send on either protocol, transitioning into the respective new one, by attaching a discriminant to the beginning of their message which indicates the target protocol. This achieves our goal of concurrent communication.
The implementation details are the most interesting part.
- If you must send a discriminant alongside every message, even when it (might?) be unambiguous which protocol it is intended for, it could be inefficient. Perhaps there is a way to compromise, with a special session type even?
- It should be possible to "pack" a
choose()over either one of these protocols into the same byte or bytes preceeding the message. - How could both sides of the channel negotiate to "escape" from the duplex?
- How would nested a
Duplexfunction? There would have to be some sort of intermediate state to keep track of the fact that the discriminant hasn't been sent, or has been sent...
Design 2: Take 2
Duplex<S, Q> is a session type which allows both S and Q to be interacted over concurrently via a single channel. Its Dual is Duplex<S::Dual, Q::Dual>. However, Nest/Escape is prohibited within this to avoid the need to deal with environments early on in this design.
The core principle is that you can select either protocol and immediately act through it if we're expected to Send or Choose with it. We can abstract this away using some form of Writer session type. .select1() on a Duplex<S, Q> will return a Writer<S, ((), Q)> after sending a discriminant over the channel, which will allow you to perform an operation such as .send(T) if S were Send<T, R>, returning a Duplex<R, Q>. .select2() acts differently but as you'd expect.
Recv/Accept is different, because you cannot selectively receive on either protocol without a buffer, which would defeat the purpose. So, you must .obtain() on the Duplex<S, Q> to attempt to read a discriminant from the channel. If it succeeds, it will either call your handler for Reader<S, ((), Q)> or Reader<Q, (S, ())>. With this, you can .recv() or .accept() as you desire, which will transform this back into the appropriate Duplex.
Remaining concerns
- ~~How should we escape from
Duplexsessions?~~ Resolved:Duplex<S, Q, F>where.exit()is defined overDuplex<End, End, F>which returnsF. - ~~It'd be nice if session types could implement things like
.send()over themselves instead...~~ Not possible. - I need to form a trait heirarchy for session types to prohibit or permit certain compositions, both for coherence purposes and for safety reasons (as above).
- ~~If we could prove to the compiler that it's impossible, under any scenario, for the opposite end of the channel to be in
Recvwithout an interaction from us, we could avoid a discriminant in many situations. (The discriminant should only serve as a disambiguation when neceessary.)~~ Resolved using barriers (will explain later). - It still should be possible, somehow, to "compress" the discriminant of a
.select1().choose()into a single byte. - ~~How should we handle "closing" channels?~~ And "waiting" operations? *update: * Never
.defer()on anEnd. - How should we anticipate "waking" up channels for async purposes? As an example, in a relay network, one peer will send us data that we need to broadcast to all of the other peers. However, our channels will be "asleep" until
mioor something else wakes it up. I need to prototype something running onnemoto fully understand the library design before I start adding crazy features likeDuplex...