Channel-Less Process Communication

A channel is an abstract data structure which allows for passing messages from one process to another one. We propose several variants of OCCAM, a minimalistic programming language in which a program consists only of processes and channels. The variants differ in how channels are accessed by processes. We prove that all these variants are equally expressive, i.e. an arbitrary OCCAM program can be simulated in any of the variants and the other way around. A particularly interesting variant is to assign exactly one channel to each parallel process. This makes the concept of channels redundant, provided that the parallel processes are named. The simulation techniques can be applied to a variety of abstract models and practical systems.

The main contribution of this paper is showing a transformation of a channel-based programming language to an equally expressive programming language in which channels and processes become a single entity. We illustrate this transformation on OCCAM [5], [15], which is is based on the synchronous abstract model CSP (Concurrent Sequential Processes) [1] and belongs to practical programming languages whose semantics has been formally defined [16], [17], [18], [19], [20]. Although OCCAM is "pure and small", a sharper Ockham's razor trims it even more. Along with channels, we also eliminate nesting of parallel processes and the ALT constructor from OCCAM.
The motivation of this work is not solely theoretical. Many programming languages build on a message passing paradigm without channels, e.g. MPI [21], Erlang [22] and Akka (JVM) [23]. An important question is whether the absence of channels is restraining. This paper suggests a negative answer. A consequence is that channel-based (OCCAM-like) programming languages are intrinsically redundant.
The paper is organised as follows. Section II presents a relevant subset of the OCCAM language (leaving out unnecessary details, occasionally using an abbreviated syntax). In Section III, it is shown that nesting of parallel processes can be replaced by a flat process structure (a variant OCCAM-1PAR). In Section IV, the directional graph interconnection of processes and channels is replaced with a hypergraph This research has been supported by the grant 1/0601/20 of the Slovak Scientific Grant Agency VEGA. interconnection which uses shared channels (OCCAM-SH). In Section V, a concrete hypergraph structure is proposed which leads to a unification of process and channel identifiers, i.e. to a channel-less model (OCCAM-CL). All the variants OCCAM, OCCAM-1PAR, OCCAM-SH, and OCCAM-CL are equally expressive. Section VI concludes the paper.

II. OCCAM
OCCAM was targeted to Transputers [24], single-chip computers specifically designed to support parallel programming. Transputers could be easily connected to form a network, process scheduling and communication were implemented in the hardware. Although Transputers were discontinued in 1990's, they could in some parameters compete with contemporary computers (e.g. context switch below 1 µsec still belongs to the fastest ever). OCCAM found its followers, e.g. OCCAM-π [7] and Rain [8].
An OCCAM program consists of a finite number of processes and a finite number of channels; these numbers are known before the program starts and do not change in runtime. A process in OCCAM is either an atomic process (:=, assignment; ?, input from a channel; !, output to a channel; SKIP, which does nothing and terminates), or a compound process. Constructors of compound processes (SEQ, PAR, IF, WHILE, ALT) combine processes into a single one. Processes in the SEQ constructor are executed sequentially in the given order, i.e. when a process terminates, the following one becomes active. SEQ terminates when its last mentioned process terminates. The constructors IF, WHILE and the assignment process behave in a common way, except for IF, in which the conditions are always tested in the order they are written, and (only) the process under the first satisfied condition becomes active (IF terminates when this process terminates).
The replicator FOR can be used with constructors. Replication works as a macro expansion. For example, SEQ i=0 FOR 2 p expands to SEQ p p, with i = 0 in the first replica of the process p, and i = 1 in the second one. This corresponds to a sequential repetition of the process p in a for-loop.
The scope of a variable is limited to the process following the variable's declaration (e.g. INT v:), including processes nested in that process (variables used in replicators are not declared). Usual primitive types are available. The only compound type is an array, indexed from 0.
Processes of the PAR constructor run in parallel and have read-only access to variables which are shared in their scopes. A process is allowed to change (using := or ?) only the variables which it does not share with another parallel process. The only way how parallel processes may influence one another is via channels which are declared as variables of type CHAN. PAR terminates when all its processes terminate.
Channels in OCCAM are unidirectional and unbuffered. Each channel connects exactly two parallel processes-one reads from the channel, the other one writes to the channel. Parallel processes can be depicted as vertices and channels as edges of a directed graph (an edge points from the writer to the reader). Reading from a channel (?) blocks until a ! process writes to the channel; and conversely, writing to a channel (!) blocks until a ? process reads from the channel). The corresponding ? and ! both terminate after the message has been transferred across the channel from the ! process to the ? process. A message is a finite sequence of values (of primitive types or arrays). 1 The ALT constructor multiplexes reading from several input channels. 2 While reading from each single input channel would block, the ALT constructor blocks. When reading at least one input channel would terminate (we say that such a channel is readable), a message is read from one readable channel and a process in the corresponding branch takes over. When this process terminates, then ALT terminates.
The choice of the readable channel inside the ALT constructs and the scheduling of the parallel processes is not under program's control. The scheduler executing the program is unpredictable (nondeterministic) in making these choices. This does not mean that it has to be "random". For example, it is allowed to (but does not have to) prioritise the readable channels in the order they are mentioned in ALT constructs. Similarly, it is allowed to (but does not have to) prioritise the execution of active parallel processes in the order they are mentioned in PAR constructs. A correct program must guarantee its intended behaviour for all possible schedules.
Processes and channels are static, i.e. they are constructed before the execution of a program. During the execution, each process is in one of the following states: active, passive, blocked. The program evolves according to the rules described above (see [18] for details). At the beginning of the execution of a program, the first process is active, all other processes are passive. A termination of an active process does not mean that the process ceases to exist-it just changes its state to passive. The program execution terminates when there are no active processes. (We can distinguish between a "correct termination" where all processes are inactive, and a deadlock where at least one process is blocked.) 1 Although OCCAM requires a declaration of types of messages which are passed over channels (so-called protocols, e.g. CHAN OF INT; BOOL ch:, we consistently use type-less channel declarations throughout the paper. These correspond to CHAN OF ANY declarations in OCCAM, where the programmer is responsible for ensuring that the sequences of values in messages transferred over a channel from a ! process are of the same types as the corresponding variables in a ? process (so that an incoming message can be stored to the variables in the ? process). 2 For the sake of simplicity, we do not consider ALT with boolean expressions attached to the channel inputs (so-called guarded ALT). For an arbitrary schedule of P 1 , a schedule of P 2 exists such that the orderings match, and vice versa. Moreover, there is a bijective correspondence of processes which run in parallel in P 1 and P 2 , regardless of their nesting in PAR constructs (we refer to line numbers in P 1 and P 2 ): [5,5], [11,12], [12,13]. In this sense, P 1 and P 2 are equivalent.
Program In the sequel, we only deal with one-sided simulations in which P is constructed from P by replacing its fragments of code. In all proofs, we give a construction of P which preserves parallelism of P (as the simulations are one-sided, we only insist on an injective mapping of parallel processes of P to P ; e.g. additional parallel processes can be added to P in a simulation. A programming language L is at least as expressive as a programming language L , if there is an algorithm (compiler) which translates an arbitrary program P in L to a program P in L which simulates P . Two programming languages L and L are equally expressive, if L is at least as expressive as L , and vice versa.

III. OCCAM WITH A SINGLE TOP-LEVEL PAR CONSTRUCTOR (OCCAM-1PAR)
Theorem 1: OCCAM with a single top-level PAR constructor (OCCAM-1PAR) is as expressive as OCCAM.
Proof: We need to show that an arbitrary OCCAM program can be simulated by an OCCAM program which uses exactly one PAR which is the top-level process. Consider an arbitrary OCCAM program. All channel declarations are moved to the top-level PAR (collisions of channel names are resolved by using fresh names). We will refer to processes of PARs in the OCCAM program as child processes. Each child process is moved to the top-level PAR, together with declarations of all variables in its scope (including shared variables used in replicators). A new local integer variable terminate is declared in the child process; and two new channels are declared in the top-level PAR, we will call 516 PROCEEDINGS OF THE FEDCSIS. SOFIA, BULGARIA, 2022 them ch.s (start) and ch.e (end). A child process is started when it receives a message beginning with FALSE from the channel ch.s. This message also contains values of all the variables shared for reading between the parent and the child. When the child finishes its original program, it sends an acknowledgement to the channel ch.e. The parent (the sequence which replaces the PAR) starts its children and waits for their acknowledgements. Just before its own termination, the parent terminates its children. Fig. 2 shows the translation of the program P 1 (Fig. 1) to an OCCAM-1PAR program P 3 with a single top-level PAR.

IV. OCCAM WITH SHARED CHANNELS (OCCAM-SH)
Recall that a channel in OCCAM connects two parallel processes. OCCAM-SH is an interesting variant in which channels are shared, i.e. a channel can be simultaneously accessed by arbitrarily many parallel processes for both reading and writing. When several ! processes simultaneously write to a channel, then they are blocked until a ? process reads from the channel. When several ? processes simultaneously read from the same channel, they are blocked until a ! process writes to the channel. When one or more ? processes read from a channel and one or more ! processes write to the channel, then eventually one of the ? processes and one of the ! processes are chosen for communication. This choice is made arbitrarily (i.e. the scheduler can freely decide which processes it chooses for communication). Then the message is passed from the chosen ! process to the chosen ? process and then these two processes terminate. Unlike in OCCAM, there is no ALT constructor in OCCAM-SH. Furthermore, OCCAM-SH programs use only one top-level PAR constructor.
It turns out that in spite of the absent ALT constructor, OCCAM-SH is a generalisation of OCCAM. We will show how an arbitrary OCCAM-1PAR program can be translated to an OCCAM-SH program which simulates the former one. This translation requires that the parallel processes and channels have identifiers. The order in which a parallel process appears in the single PAR constructor) will serve as its identifier. Analogously, channels are numbered in the order they are declared (to keep the notation simple, a channel identifier will serve as the channel's number). Let the numbering start with 0, let N denote the number of processes.
Theorem 2: OCCAM-SH is at least as expressive as OC-CAM.
Proof: We have already proved that an arbitrary OCCAM program can be simulated by an OCCAM-1PAR program. It remains to show how the ALT constructors of OCCAM-1PAR are simulated in OCCAM-SH. Consider an OCCAM-1PAR program. For each parallel process p, merge all the channels from which the process reads to a single channel ch.in p . This shared channel will be the only one which the process p will read, and p will be its only reader (there may be more than one writer, though). Declare arrays INT pending.ch[N] and MSG pending.msg [N] in the scope of each parallel process, where MSG is the type of messages transferred in the OCCAM-1PAR program. Initialise SEQ −− original top-level code 5: SEQ i = 0 FOR 2 6: BOOL ack: 7: SEQ −− replacement of parent PAR 8: ch.s1 ! FALSE; i −− start child 1 9: ch.s2 ! FALSE; i −− start child 2 10: ch.s3 ! FALSE; i −− start child 3 11: ch.e1 ? ack −− wait for end of child 1 12: ch.e2 ? ack −− wait for end of child 2 13: ch.e3 ? ack −− wait for end of child 3 14: ch.s1 ! TRUE; 0 −− terminate child 1 15: ch.s2 ! TRUE; 0 −− terminate child 2 16: ch.s3 ! TRUE; 0 −− terminate child 3 17: BOOL terminate: 18: INT i: 19: SEQ −− child 1 of PAR 20: ch.s1 ? terminate; i −− wait for parent 21 where r is the identifier of the process which reads the channel ch in the original OCCAM program.
Replace each ch ! m of the parallel process w with ch.w sh.ch [w] ! m.
Insert ch.r sh.ch [p] ! TRUE at the end of each parallel process p of the OCCAM program.

V. CHANNEL-LESS OCCAM (OCCAM-CL)
OCCAM-SH1 is a stricter variant of OCCAM-SH in which each parallel process p is allowed to read only from one channel ch.in p , whereby p is the only process which reads from the channel ch.in p . We call this variant channel-less, because the identifiers of channels unify with the identifiers of processes. The processes can be numbered in the order they appear in the single PAR constructor.
Proof: An OCCAM-SH1 program is also an OCCAM-SH program. Conversely, consider an arbitrary OCCAM-SH program. Translate it to OCCAM and then back to OCCAM-SH using compilers from the proofs of Theorem 3 and Theorem 2. This yields an OCCAM-SH1 program.
OCCAM-CL syntactically removes the redundancy related to channels from OCCAM-SH1. An arbitrary OCCAM-SH1 program can be rewritten to OCCAM-CL as follows: • Remove all channel declarations.   ? ack Fig. 3. Hand-made translations of the OCCAM program P 1 (Fig 1) to OCCAM-SH1 (left) and OCCAM-CL (right)

VI. CONCLUSIONS
We proposed a programming language OCCAM-CL which differs from OCCAM (only) in having no nested PAR processes, no ALT constructors, and-most importantly-no channels. In spite of this, OCCAM-CL is as expressive as OCCAM. We proved this using several OCCAM variants and compilers which translate programs from one variant to any other one. These compilers preserve parallelism of programs as well as their message complexity (up to a constant multiplicative factor). A similar result was published in [25] for a lambda calculus with typed asynchronous channels and a lambda calculus with typed actors.
When it comes to writing an actual compiler, the choice between OCCAM-CL and OCCAM is not just a matter of taste. Apparently, writing a parser for OCCAM-CL is easier, but there are more subtle reasons for favouring OCCAM-CL. For example, OCCAM requires that each channel connects exactly two parallel processes (one reader, one writer). However, its syntax does not prevent the programmer from violating this requirement. It is up to the compiler or a run-time system to detect such a violation.
The channel-less approach can be found in actor models [26] as well as in contemporary programming languages, e.g. Erlang and Akka. A subsequent extension of Akka with the channel concept is in the light of our results a backward step. It does not increase expressiveness, unnecessarily increases the complexity of the language and the compiler, and increases the structural complexity of programs which mix the channel and channel-less paradigms.