Asynchronous Sessions with Implicit Functions and Messages

Session types are a well-established approach to ensuring protocol conformance and the absence of communication errors such as deadlocks in message passing systems. Haskell introduced implicit parameters, Scala popularised this feature and recently gave implicit types first-class status, yielding an expressive tool for handling context dependencies in a type-safe yet terse way. We ask: can type-safe implicit functions be generalised from Scala's sequential setting to message passing computation? We answer this question in the affirmative by presenting the first concurrent functional language with implicit message passing. The key idea is to generalise the concept of an implicit function to an implicit message, its concurrent analogue. Our language extends Gay and Vasconcelos's calculus of linear types for asynchronous sessions (LAST) with implicit functions and messages. We prove the resulting system sound by translation into LAST.


I. INTRODUCTION
Session types.Types classify programs, distinguishing between programs that are guaranteed to exhibit semantic properties of interest, and those that are not.Types for sequential computation are well-established and a core part of industrial software engineering.Behavioural type systems extend types to concurrent, parallel and distributed computation, and are a core activity of contemporary type theoretical research.
Session types, first introduced in [3], [10] are an important example of a behavioural type system for message passing concurrency.Session types classify message passing behaviour at given channels: e.g. if process P first receives an integer and then a boolean on channel x, and finally sends a boolean on x, then this behaviour could be expressed by the session type !Int.?Bool.!Bool.endHere ?T represents input of a value of type T, !T means sending a value that has type T, while end denotes the end of the interaction.
A key notion in session types is that of duality, originating in linear logic: processes P and Q can be composed in parallel only when throughout the course of the computation each output of P 's is matched by a suitable input of Q's, and vice versa.Session types allow only dual processes to interact.Hence typability guarantees the absence of communication errors such as mismatched communication and deadlocks.A process Q, dual to P above, would have the session type ?Int.!Bool.?Bool.endNotice that for each action in P 's type, we have the dual action in Q's type, e.g. an output of type !Int can be received by an input of type ?Int.
Implicit functions.Modularity, a core concept in software engineering, is greatly aided by parameterisation of programs.Parameterisation has dual facets: supplying and consuming a parameter.A key tension in largescale software engineering is between explicit (e.g.pure functional programming), and implicit parameterisation (e.g.global state).The former enables local reasoning but can lead to repetitive supply of parameters.Here is a simple example of the problem (where <= is the function λxy.x ≤ y, and α a type): let f x compare : α = ... in f 3 (<=) ... f 17 (<=) ...
Repeatedly passing functions like <= which are unlikely to change frequently, is tedious, and impedes readability of large code bases.Default parameters are an early proposal for mediating this tension in a type-safe way.
The key idea is to annotate function arguments with their default value, to be used whenever an invocation does not supply an argument: The compiler synthesises f 2 (<=) from f 2, and f 5 (<=) from f 5. Default parameters have a key disadvantage: the default value is hard-coded at the callee, and cannot be context dependent.Implicit arguments, a strict generalisation of default parameters, were pioneered in Haskell [6], and popularised as well as refined in Scala [7]: they separate the callee's declaration that an argument can be elided, from the caller's choice of elided values, allowing the latter to be context dependent.
In this example f 2 is rewritten as above, but f 5 becomes f 5 (>), i.e. a different implicit argument is synthesised.The disambiguation between several providers of implicit arguments happens at compiletime using type and scope information.Programs where elided arguments cannot be disambiguated at compiletime are rejected as ill-formed.Hence type-safety is not compromised.
One might ask: can type-safe implicit functions be generalised from Scala's sequential setting to message passing computation?We answer this question in the affirmative by generalising the concept of implicit functions to implicit messages.We elaborate on this idea by presenting the first concurrent functional language with implicit message passing: we extend Gay and Vasconcelos's calculus of linear types for asynchronous sessions (LAST) [2] with implicit message passing and implicit functions.We argue with several examples that implicit messages provide useful abstractions for programming languages with session types.In particular, repeated rebinding of session names can be omitted.

Implicit messages. The concept of implicit messages has two dual parts:
• Input can be declared implicit, and not be explicitly matched by an output in the dual process.• At compile time, a suitable output is synthesised, based on type and scope information.In the following example, we have two processes p and q running in parallel (we write for parallel composition).They initiate a session (denoted by accept and request ) on fresh and dual channels c (for messages from p to q) and d (for messages from q to p), whereafter p performs an (implicit) receive and q apparently does nothing.The type system sees the implicit receive in p and is able to figure out that a corresponding send must be inserted into q.It knows that the channel that the send occurs on is d since it is the dual channel to c which the implicit receive uses.The chosen message is a variable of appropriate type from the implicit scope.The implicit scope can be thought of as a store of variables that are designated as implicit -we make this notion more precise in Section IV.Following [7], we do not give names to implicit variables until after translation, but use (pronounced 'query') as a placeholder name for all implicit variables.The translation becomes: Here y is a fresh variable.This insertion corresponds to adding an implicit variable as an additional argument to an implicit function.
Elimination of repeated rebinding.A well-known problem with the integration of session types and sequential languages is the seeming necessity of repeated rebinding of channel names.The problem is that send takes a channel of type !T.S as its second argument, and returns a linear channel of type S. In order for linearity to be respected that channel must be rebound.Consider the process below, typical of LAST programs.This redundancy makes programs hard to read.The issue can be addressed in other ways, for example using parameterised monads [1], see also [2,Chapter 7].Implicit functions and message passing enable a principled and canonical solution: make the channel argument implicit and let the compiler synthesise the missing channel name for rebinding.
The send primitive has type T →!T.S → S. We can use implicit function types to define a new output primitive send , with type T →!T.S → S, explained in detail below.The annotation in !T.S → S makes the channel argument implicit, and the returned channel is rebound to the implicit scope by the body of send and is not required elsewhere.We can rewrite miscService above with our new primitives.The resulting code is less repetitive and more terse, hence readable.
Session type classes.Type classes [5], [11] provide typesafe ad-hoc polymorphism.They allow the programmer to define a fixed set of functions over multiple datatypes, where each datatype has a bespoke implementation of each function in the set.We call these sets of functions type classes.They are usually implemented by dictionary passing [11].That means that at compile time an additional argument (the dictionary) and suitable access to this argument are synthesised for all code depending on type classes.With implicit arguments we can make dictionary passing implicit, and type classes become a special case of implicit arguments.This is a common Scala idiom [8].
Implicit messages suggest a natural generalisation of type classes: pass access to dictionaries by implicit messages!We illustrate this with an example.In Haskell, Show is a type class that converts values to string representations.We generalise this to message-passing concurrency: instead of a conversion function, we have a conversion server.We show example implementations intShow and boolShow (with some details omitted).Additional function servers can be written against this code over types that define a Show type class server.Clients communicating with the show server such as showUser do not need explicitly to send their show implementation, but send one implicitly.
It would be possible to make this example even more terse by eliminating repeated rebinding with implicit functions, however for clarity we show just one application at a time.

II. THE LANGUAGE IM
This section presents our language IM of implicit message passing.IM is an extension of Gay and Vasconcelos's calculus of linear types for asynchronous sessions (LAST) [2].(Familiarity to LAST will be essential for understanding the rest of the paper.)LAST is a λ-calculus with primitives for spawning threads that exchange messages.LAST was the first coherent integration of session types with λ-calculus and uses linear types at the λ-level to mediate between session types and functions.LAST is a suitable foundation for implicit message passing because its smooth integration of functions and processes enables us to provide both: implicit functions and implicit messages.
As the compiler synthesises the missing arguments at compile-time from type information, calculi for implicit arguments might be best understood not as programming languages, but as meta-programming systems that generate code in a base language L from input programs in L with implicits.Indeed, SI [7], an extension of System F, Scala's foundations for implicits, does not have a selfcontained operational semantics, and is instead compiled to System F. We use the same approach, and translate IM to LAST.
Syntax.In the presentation of IM's syntax, let v range over values and e over expressions.We assume that x ranges over a countable set of term variables, c over a countable set of channel endpoints, n over N ∪ {∞}, l over labels and I over finite subsets of N. In order to make the presentation easily accessible, we highlight the extensions IM adds to LAST.
Here implicit receive is the implicit analog of receive.Unlike receive, it is not matched by a corresponding send, but a corresponding send is inserted during translation, while implicit receive is translated into a normal receive.denotes a query to the implicit scope. is removed at translation time, and is replaced by a nondeterministically chosen name in the implicit scope.The construct let x, = ... allows us to add variables to the implicit scope, and as with the lone , we also replace within let by a variable name during translation.Note that we often write let = e in e'.This is a convenience and can be thought of as syntactic sugar for let _, = (_, e) in e' where _ is an unused variable or expression.The parameter n following accept n and request n gives a bound for session communication.This will be explained in later sections.Note that we omit the bound parameter for brevity where not relevant.
An IM program is a configuration of expressions in parallel, running as separate threads and typed in a suitable environment.We now define configurations, ranged over by C.

III. TYPES FOR IM
Just as SI is given meaning by type-guided translation to System F in [7], we give such a translation of IM into LAST.This section prepares the translation by extending LAST's typing system with types for implicit message passing and implicit functions.Types for IM are given by the following grammar.Here T ranges over types for the λ-calculus part of IM, S over session types, and B over buffer types.
The type T → T is the type of implicit functions.It is written ?→ in [7] but we replace ?by to avoid confusion with the input session type ?T.S.The type T T is the linear equivalent of T → T .As with [7], we do not have syntax for implicit abstraction and application -these are inferred during implicit resolution in Section IV.
The types !T.S and ?T.S are the types of implicit message input and output respectively.They are the dual of one another as with explicit output and input.Implicit output types cannot be deduced from a process's syntax (since they are implicit) and must be inferred by inspecting the process that contains the corresponding implicit input.This happens during implicit resolution.
Buffer content types B are composed of vectors of entries B. Each entry is either a type T , representing the type of a value that is to be sent and stored in the buffer, or a label l representing the selection of such an option l by a process communicating using the buffer.Buffer content types B are assigned to buffers b such that for each v in b there exists a type T in the corresponding buffer content type B such that v : T .This notion is made precise in Section IV.
Given below are the type schemas for the constants k.They are the same as LAST's, and can be instantiated for any appropriate type.
Note that we omit a type schema for implicit receive.This is because it cannot be translated by the rule [T-CONST] in Figure 1, but needs a bespoke typing rule as unlike the other constants its translation is not identity.We now give the session type duality function for our calculus.If a session type S and S are dual, written S = S , then a pair of terms of types S and S can interact without communication errors.Such processes match in the sense that every action that one takes is matched by the other -if one outputs, the other inputs.If one offers a choice, the other makes a choice.We Let S denote the set of contractive, closed session types, and let T denote the set of types in which all session types are contractive and closed.We now define the function F (•) on binary relations over T .We omit all cases not involving the new type constructs.The remaining clauses are formally identical with LAST's.

(T → T , T T )|T, T ∈ T } ∪ {(T → T , T → T )|T, T ∈ T } ∪ {(T T , T T )|T, T ∈ T }
Contractivity ensures that F is monotone.We write T <: U if the pair (T, U ) is in the greatest fixpoint of F .The last two lines in the definition of F (•) allow us to type sending explicit messages to implicit input.The matches relation determines whether a given buffer type B agrees with a session type S. We write B mat S when the types in B match a prefix of those in S. We formalise this notion with the rules below: Next, we define bound(S), which gives the bound of a session type, an upper bound on the runtime size of the buffer required to hold the values received on a channel with session type S. We start with the auxiliary operator bds

IV. TRANSLATION FROM IM TO LAST
This section presents implicit resolution, the typedirected translation of IM programs to LAST.We proceed in three steps, translation of expressions, translation of buffers and translation of configurations.Following [7], the translation is type-directed in that we give typing rules for IM, instrumented with translations to LAST.By forgetting the instrumentation, we obtain a typing system for IM.
Typing environments and implicit scope.Implicit resolution removes queries and inserts explicit functions and messages in place of implicit ones.This happens by choosing arguments from the implicit scope.We define the implicit scope thusly: The typing environment Γ is divided into two parts: the implicit and explicit scopes.
That is to say, some of the bindings in Γ refer to implicit variables and some to explicit variables.In our typing rules we range over implicit variables with y and explicit variables with x.Variables enter the implicit scope in several ways: (1) when received as an implicit message; (2) when given as an argument to an implicit function; and (3) when bound by a let construct with on the left-hand side of the =.
Typing and translation of expressions.Typing judgements for expressions are of the form Γ e : T Y e.This can be read as "under assumptions Γ, the expression e has type T and is translated to the LAST expression e".
Our typing and translation rules can be found in Figure 1.With the exception of the new syntactic forms of expressions, the translations are homomorphic, yielding rules similar in structure to those found in [2].The rules  for our new syntactic forms are more interesting.

The rules [T-SPLITI], [T-APPI], [T-ABSI] and [T-QUERY]
follow a similar structure to those in [7].Note that with [T-QUERY], the variable chosen to replace must satisfy linearity constraints, a restriction not present in [7].

[T-ABSLI] is a linear version of the rule for implicit functions and is effectively a combination of the rules [T-ABSI] and [T-ABSL]. The rule [T-INI] translates implicit receive into receive and otherwise behaves in the same way as [T-CONST]. [T-
OUTI] translates implicit outputs by inserting a send action into the process.The argument for the send is a variable from the implicit scope, which we get from the first premise by translating with (a subset of) the input environment.This yields an implicit variable with the appropriate type whilst also satisfying any linearity constraints.Note that [T-OUTI] is the only rule adding outputs directly.
Typing and translation of buffer contents.Typing judgements for buffers follow the same form as typing judgements for expressions.We write Γ b : B Y b.The translation of buffers can be found in Figure 2.
Typing and translation of configurations.Typing judgements for configurations (Figure 3) follow a slightly different form to those for buffer contents and expressions.We write Γ C £ Δ Y C.This can be read as "under assumptions Γ, the configuration C yields buffer types Δ and is translated as C".We define buffer type maps Δ below in Definition 2. [2], augmented with homomorphic translations.The rule [T-PAR] is also similar to its equivalent rule in [2], but also contains two new premises.The first computes the buffer types in the configuration C 1 C 2 , which are used in the second premise to perform implicit resolution.The judgements used in these premises are explained below.

The rules [T-THREAD], [T-BUFFER] and [T-NEW] are as in
We introduce a derivation of the form S 1 S 2 S 1 , S 2 .This judgement can be understood as saying that session types S 1 and S 2 are compatible if we rewrite them as S 1 and S 2 .The rule [C-SUB] captures the LAST notion of compatibility.In this case no augmentation of the compared types is required.The rule [C-IMP] accounts for implicit behaviour.Intuitively this rule says that if two session types are compatible except that one has an implicit input unmatched by the other, we can augment the other with a corresponding implicit output and judge them compatible.The [C-REV] rule captures the symmetry enjoyed by the compatibility relation of LAST.
Buffer types are triples of the form (d, n, B).We let Δ range over partial finite maps from channel names to buffer types in C. Δ + Δ means that the domains of Δ and Δ are disjoint.
DEFINITION 3. We define a partial operation of addition on environments: T-SEQV Fig. 2. Type guided translation of buffers contents.
We extend this to Γ + Γ inductively from the base case.
DEFINITION 4. We say that Γ and Δ resolve to Γ , written Γ, Δ resolve Γ provided there are environments Γ p and Γ s with disjoint domains such that: • Γ p has the property that ∀c, d ∈ dom(Γ)∩dom(Δ): The buf relation Γ C buf Δ computes the buffer types present in a configuration C. In order to determine where output actions must be inserted during implicit resolution, we first determine those channels over which session communication occurs.The buf relation determines this by inspecting the structure of the configuration C and collecting the names of channels used for session communication, and their associated buffer types, into the map Δ.The rule [B-NEW] removes from the map Δ those channels that are restricted by a (νcd) construct.[B-BUFFER] extracts a channel name and associated buffer type using the environment Γ. [B-PAR] combines results across parallel composition.[B-THREAD] an empty base case.The buf rules can be found in Figure 4.

Sources of nondeterminism.
There are two sources of nondeterminism in implicit resolution.The first is in the selection of the implicit variable chosen by the rule [T-QUERY].We do not specify which variable in the implicit scope should replace a .A possible way to resolve this is to use nesting.Such a solution would select the innermost implicit variable of the appropriate type as the translation for .The Scala compiler uses a more complex version of this strategy, augmented with other selection criteria [7].
The second source of nondeterminism results from the insertion of output actions when resolving implicit messages.When a pair of composed processes are resolved, we do not specify which is resolved first.As a result, adjacent implicit inputs can be resolved in multiple ways.Consider the processes: Implicit resolution should insert two output actions here, one in p and the other in q.If we resolve p before q, we obtain the processes: We could also resolve q first and obtain the processes: As with nondeterminism caused by resolution of , an implementation could use a simple heuristic such as to resolve the accepting partner before the requesting partner.We leave this question for future work.

V. RUNTIME SAFETY OF IM
We prove safety of IM's translation into LAST: we show that if we can derive Γ C £ Δ Y C in IM, then C can be typed suitably in LAST.In order to make this precise, we define a function (•) * , that translates IM's types to standard LAST types.This translation simply erases occurrences of , yielding non-implicit analogues of implicit types.
DEFINITION 5 (Translation of types).We call a configuration fully buffered if whenever it contains c → (c , n, b) then it also contains c → (c, n , b ).
We recall the following theorem from [2], defining and proving LAST's runtime safety.

VI. FURTHER WORK
Implicits are useful in sequential programming not just for type classes, but also e.g. for generic programming [9].With implicits at hand, it is possible to investigate generic programming in message passing systems.Such technology transfer from the sequential to concurrent computation would be aided by a better understanding of the relationship between implicit functions and implicit messages.Finally, we conjecture that implicits are not tied to binary sessions, and can be adapted to multi-party session types [4].
accept x in let n, c = implicit receive c in c in p let q = let = 10 in let d = request x in d in q miscService :: S a → end miscService ap = let c = accept ap in let m, c = receive c in let n, c = receive c in if pred(m) then let c = select l1 c in let c = send f(m, n) c in let c = send g(m, n, n) c in c else let c = select l2 c in let o, c = receive c in let c = send f(n, m) c in let c = send g(m, n, o) c in c send :: T → !T.S → S → U → U send m u = let = send m in u We can do something similar for select and receive.select :: Label → ⊕ ...l:S, ... → S → U → U select l u = let = select l in u receive :: ?T.S → T receive = let m, = receive in m type Show = ?a. ??a. !String.end a .!String.end show :: Show a → end show c boolShow :: ?Bool.!String.end a → end boolShow c = let c = accept c in let b , c = receive c in send (if b then "true" else "false") c implicit intShow :: ?Int.!String.end a → end intShow = ... showUser :: Show r → end showUser ap = let c = request ap in let c = send 10 c in let s, c = receive c in printf(s) ; c extend the duality function of LAST to include the two forms of implicit communication: ?T.S = !T.S !T.S = ?T.S ?T.S = !T.S !T.S = ?T.S μX.S = μX.S X = X⊕ l i : S i i∈I = & l i : S i i∈I end = end & l i : S i i∈I = ⊕ l i : S i i∈IWe now define the subtyping relation coinductively by extension of the definitions for LAST.DEFINITION 1.A type T is contractive if it does not have subexpressions of the form μX 1 ...μX n .X i where 0 < i ≤ n.
& ..., l : S, ... M-CASE For some S and B such that B mat S, S/ B gives the session behaviours remaining as a postfix of S after performing those behaviours that correspond with B. We define the postfix operator below: S/ = S ?T.S/U B = S/ B ? T.S/U B = S/ B & ..., l : S, ... /l B = S/ B x : T e : U Y e Γ λx.e : T U Y λx. e T-ABSL un(Γ) Γ, α : T α : T Y α T-ID Γ1 e : & li : Ti i∈I Y e ∀i∈I (Γ2 ei : Ti T Y ei) Γ1 + Γ2 case e of {li : ei}i∈I : T Y case e of {li : ei}i∈I T-CASE Γ e : ⊕ li : Ti i∈I Y e j ∈ I Γ select lj e : Tj Y select lj e T-SELECT Γ, y : T e : U Y e y fresh Γ e : T U Y λy. e T-ABSLI Γ1 e : T U Y e Γ2 : T Y y Γ1 + Γ2 e : U Y e y T-APPI Γ, y : T e : U Y e y fresh un(Γ) Γ e : T → U Y λy. e T-ABSI Fig. 1.Type guided translation of expressions.
let = ... let d = request x in let n, = implicit receive in d in q let n, c = receive in c in p let q = let y = ... let d = request x in let n, d = receive in d send y d in q
THEOREM.Let Γ LAST C £ Δ be a fully buffered LAST configuration, and assume that C −→ * C .If C is a blocked thread in C , then one if the following applies: (1) C is v or send v or request n v THEOREM 1 (Runtime safety of IM).If Γ C£Δ Y C is a fully buffered IM configuration, then Γ * LAST C£Δ * is a runtime-safe LAST configuration.Proof.By induction on Γ C £ Δ Y C.
or accept n v ; (2) C is E[receive c] and c → ( , , ) ∈ C ; (3) C is E[case c of {l i : e i } i∈I ] and c → ( , , ) ∈ C .We state our main result.