2013-2014 Matteo Acerbi

Dependent sessions in Agda [WIP]

Types

The syntax for types is heavily inspired by both Strictly Positive Families (the version in Peter Morris's PhD thesis) and (indexed) Descriptions (Chapman et al, "The Gentle Art of Levitation").

In our case, however, we do not want to describe indexed functors: we just want to be able to close the language under a (greatest) fixpoint operator/binder `nu , allowing nested usages thanks to a "well-typed Bird-Paterson" encoding of binding.

To compare with the cited universes, note that if we tried to give a (I → Set) → (O → Set) semantics to the `I , `Σ , `Π , `ν codes we would have to ignore the input of type O : in any case, such an interpretation would not make much sense here.

As opposed to Wadler's CP, the typing rules for the additional constructors ⊗ , ⅋ , ¡ , ¿ will not correspond directly to those of classical linear logic: however, the corresponding syntax maintains similar operational meaning (channel transmission and client/server interactions).

We add a type called Side (endpoint of a channel), with two only inhabitants: + , - . Entry is a type of optionally "sided" (session typed) codes, where absence of a side corresponds to the session not having started yet.

When a Side is specified, the Entry corresponds to an obligation for the process that contains it in its (input) context: if it is + then the session type is to be interpreted without changes, otherwise the process must behave along that channel by following the dual obligation (receiving vs sending, reading vs writing, connecting vs accepting).

Note that for what concerns _⊗_ and _⅋_ a whole Entry is required as the description of how a process participates to a channel needs to be preserved by the transfer.

Exiting a session

Writing and reading values

Dependency on previously exchanged values

Sending and receiving channels

Servers and clients

Corecursion

This representation of the language of types should not be considered definitive but it seems reasonably effective.

Other approaches, where the session types inductive definition _▹_ would only have one parameter (as in Descriptions), seemed more difficult to implement and led to more awkward encodings of the syntax: we probably did not try all the possibilities, though.

Types and sides

A "sided" type former (code) [_]F is the type former (code) F when the side is + , the dual of F when it is - .

Contexts

A context is a simple list of entries, i.e. codes paired with an optional side. Absence of the side corresponds to the session not being started yet: in this situation we say that the channel is "new".

is¿ means "is it safe to duplicate this entry?".

To construct a server we must check (at compile time) that it will be safe to duplicate all the channels that its runtime copies will have access to: All¿ is the predicate over contexts which corresponds to this condition.

Splitting contexts

We need to define what it means to split an (input) context in two.

At fork time, if Γ is the input context, the user is asked to inhabit a Splits Γ , whose meaning as a pair of contexts is given by splits .

all-splits is a generic functions which allows to lift splits to "boxed" predicates.

We use some patterns to deal more easily with dependent tuples and objects of type Entry .

Terms

Introduction

Here we would like to provide a human-readable representation of the syntax which is encoded below, also discussing the choice of encoding.

For now we limit ourselves to illustrating the Agda code step-by-step.

Functors

We define several (indexed) functors: we will obtain our process syntax by taking their fixpoint. While in the future we plan to move to codes for a universe of functors such as the ones cited above, for the purpose of illustrating the method we do not want to add further complications.

Most of the productions are non-recursive, so we will first define some families in Ty .

The functor corresponding to the new grammar production/constructor simply imposes that the output context contains a new channel, i.e. a channel whose side is ε ( Maybe.nothing ).

We can send and receive channels along channels: in the former case the sent session type is omitted from the output context, in the latter the context is extended with the entry corresponding to the received channel.

Writing to and reading from channels changes the type accordingly, without necessarily introducing dependencies.

This functor "consumes" `^ from the session type: it "allows" you to choose a value from which the rest of the type is dependent, but most often this value will be forced by the type.

Session types have "identity" codes `I as leaves, so to end a session we need to consume it and remove the type from the context.

The following definitions are actual (strictly positive) indexed functors: calls to the F argument correspond to recursion in the grammar.

When forking, we need to split the input context sensibly (see Splits above): the child must do all ΓR , the continuation must do ΓL .

A server process can only be launched in a context where it is possible to duplicate all channels.

The server is located on the + side of the channel.

The client is positioned on the - side of the channel.

In this case execution is synchronous: we account for the fact that the client might also interact on some other channels by passing Δ to F .

We are free to run clients as many times as we want ( Ctr ), or even refrain from doing so ( Wk ).

To add corecursion, we must first define what we consider a guarded (hence, hopefully, productive) session.

To provide syntax to productive nested loops that run for a possibly infinite amount of time, we use the following definition:

We adopt a "tagful" syntax, where nodes of the syntax tree are made of dependent pairs whose first component is of type Tag .

The tagged family of functors

The type of the second component is given by the following family, which groups all the above functors.

Process terms

As already stated, we plan to adopt codes for indexed functors instead of defining those directly. We could then simply use a generic indexed free monad construction, as in McBride's "Kleisli Arrows of Outrageous Fortune", encoding our "Atkey-style" parameterisation in the way described there.

To make things simpler we use the following definition for now: this is neither a monad nor a monad transfomer, it is just convenient temporary syntax.

Note that referring to the above operators as "strictly positive functors" as we did, while very reasonable from their definitions, is also justified by the fact that Agda's positivity checker accepts _[_⊢_]>_ .

Syntactic sugar

Patterns

To construct and pattern match on processes we make extensive use of Agda's pattern facility.

(We plan to also define a view for these patterns, so that Agda's interactive splitting ( C-c C-c ) can be recovered after a with )

Monadic binding notation

For the binding operator we mimic Haskell's do -notation with a syntax declaration.

write and read dependently

We added `^ to recover the possibility of making use of data exchanged by write and read at the type level.

new channel for ⊤-indexed sessions

We often create channels for stateful sessions where both index sets are ⊤ : using new⊤ helps inference.

Haskell evaluator

To make an efficient use of Haskell channels, while forgetting the data required to compute their "changing" types, we resort to making a sensible use of unsafe casts. What follows should therefore be considered as an extension to the trusted base.

We provide no correctness proof for this evaluator.

We need our channels to be untyped, to obtain which we postulate an abstract type and use unsafe coercions to and from it.

This is similar to Pucella and Tov's embedding in "Haskell Session Types with (Almost) No Class".

The channels for a context.

We need some Haskell helpers.

Note that our channels are ultimately implemented on top of Jesse Tov's synchronous-channels package: for this reason, currently all the interactions are synchronous (blocking).

Every communication with the untyped channels uses unsafeCoerce .

Here is the actual test evaluator, to implement which we disable termination checking: we already use "unsafe" features so we do not bother defining a coinductive wrapper as in the IO module of Agda's standard library.

We map the embedded monadic actions to themselves, and the binding syntax to the binding operator for the IO monad.

new just creates a new channel.

fork x interprets the Splits in s obtaining two lists of channels ls and rs : it keeps ls for the parent thread and leaves access to rs to the child ( x ).

send i j writes channel i on channel j and frees the continuation from the burden of communicating along i .

We could avoid many uses of unsafeCoerce in the following but we think it is more efficient to avoid calling functions like all-ud as in the commented-out code: they perform no operations on the actual lists of untyped channels.

The usages of unsafeCoerce which are more difficult to justify are those inside every call to readUChan and writeUChan .

receive i receives a channel from channel i and allows/forces the continuation to also take care of it.

accept i a p forks a process that waits for channels: whenever one is received it spawns a new copy of the server p along it.

connect i p creates a channel and sends it along channel i , which is (or should be) shared by construction with a server, then it becomes process p .

wont i avoids starting the client/server interaction with the server which waits for channels on i .

twice i duplicates the server which waits for channels on i .

write i x writes x on channel i .

read i reads from i and returns what it read.

end i terminates the (sub)session

at/ i o simply returns o .

corec enters a loop whose body is contained in gp : the coiteration will possibly be exited whenever the value the process returns is inl x for some x .

Closed processes describe IO computations.

Note that as the evaluator allows for processes that embed any IO action one could easily add unwanted behaviours: the user is still free to capture a fragment of IO to be considered safe and simply compose this evaluator after a translation phase to IO .