Nordic Journal of Computing

THE FORK CALCULUS

KLAUS HAVELUND^{∗} Ecole Normale Supérieure, Laboratoire d’Informatique 45 rue d’Ulm, 75005 Paris, France

                         havelund@dmi.ens.fr

KIM GULDSTRAND LARSEN^{†} Aalborg University, Institute for Electronic Systems Fr. Bajersvej 7, 9220 Aalborg, Denmark

                           kgl@iesd.auc.dk

Abstract. The Fork Calculus FC presents a theory of communicating systems in family with CCS, but it differs in the way that processes are put in parallel. In CCS there is a binary parallel operator |, and two processes p and q are put in parallel by p|q. In FC there is a unary fork operator, and a process p is activated to “run in parallel with the rest of the program” by fork (p). An operational semantics is defined, and a congruence relation between processes is suggested. In addition, a sound and complete axiomatisation of the congruence is provided. FC has been developed during an investigation of the programming language CML, an extension of ML with concurrency primitives, amongst them a fork operator.

Key words: Process calculi; Process Creation; Bisimulation; Axiomatisation

CR Classification: D.3.1 [Programming Languages]: Formal Definitions and Theory – semantics; D.3.3 [Programming Languages]: Language Constructs and Features – concurrent programming structures; F.3.1 [Logics and Meanings of Programs]: Specifying and Verifying and Reasoning about Programs – specification techniques; F.3.2 [Logics and Meanings of Programs]: Semantics of Programming Languages – operational semantics.

1. Introduction

The Fork Calculus FC is motivated by problems encountered while originally investigating semantic properties of the programming language CML (Concurrent ML) [8, 9, 1]. In particular, we address the definition and axiomatisation of suitable equivalences between CML expressions. CML is an extension of ML with CCS-like concurrency primitives. The model behind CML is that of concurrently executing expressions that communicate by synchronous message passing on typed channels. The concurrent aspects

∗ On Leave from DIKU, University of Copenhagen, Denmark. The work of this author was in addition supported by the Danish ‘Forskerakademiet’. † The work of this author was supported by the Danish Natural Science Research Council project DART and partially by the ESPRIT Basic Research Action 7166, CONCUR2.

Received August 1994. Revised September, October 1994.

of CML, however, differ from CCS in an essential way: In CCS there is a binary parallel operator |, and two processes p and q are put in parallel by p|q. In CML the binary parallel operator has been replaced by a unary fork operator, and a process p is activated to “run in parallel with the rest of the program” by fork(p). The concurrency primitives of CML are provided as a set of “functions”, from which the following may be derived:

                   channel  : unit -> 'a chan
                   fork     : (unit -> 'a) -> unit
                   transmit : 'a chan * 'a -> unit
                   accept   : 'a chan -> 'a

There are minor differences in the choice of CML primitives, their types and names, in [8], [9] and [1]. In terms of the primitive features sync, send and receive of [1], which we have chosen to follow, transmit and accept

may be defined as: transmit(x) = (sync(send(x));()) and accept(x)

= sync(receive(x)). The function channel yields a fresh channel each time it is applied. The function transmit sends a value to a channel. The function accept reads a value from a channel. Finally, the function fork starts a separate evaluation of its argument function; that is: fork(f) starts the evaluation of f(). As an example, consider an implementation in CML of a function calc:real->real having the following specification: calc(x)=cos(x)+sin(x). The implementation can be given as follows:

            fun calc(x) =
                  let val r1 = channel()
                      val r2 = channel()
                  in
                    fork(fn () => transmit(r1,cos(x)));
                    fork(fn () => transmit(r2,sin(x)));
                    (accept(r1) + accept(r2))
                  end;

The function evaluates the expressions cos(x) and sin(x) in parallel. First, two (local) channels r1 and r2 are allocated. Then, the two evaluations are forked; The two forked evaluations return their respective results on the channels r1 and r2.

One goal is to define an appropriate equivalence between CML expressions. As an example, consider that we reverse the order of the two fork expressions, thus forking the sinus calculation first. Then we would like the resulting function to be equivalent to the one above. The result in both cases is two parallel evaluations of the expressions cos(x) and sin(x) and we really do not care about how this result is obtained. In general, we will not want to observe the particular forking strategy — only the collected behaviour of the result. As an even more radical example, the implementation given above should satisfy the original specification calc(x)=cos(x)+sin(x). Another natural and important requirement of our equivalence is that it is a congruence with respect to all CML constructs as this will allow compositional verification. In particular, equivalences between composite expressions should be inferable from equivalences of subexpressions.

Our work has been influenced by the work on Facile [7], a language that integrates functional and concurrent programming in a way quite similar to CML — there is for example a fork operator. In [7], Facile is given an operational semantics and a notion of equivalence is developed. The Facile equivalence is, however, not shown to be a congruence in the general case; basically due to proof-technical difficulties. The Facile equivalence may though turn out to be a congruence. We have concluded, that the issues of equivalence and compositionality in the presense of forking can best be studied in isolation from the other issues in CML, so we design a calculus, FC (Fork Calculus), that is close to CCS, but which provides a one-argument fork-operator instead of the two-argument parallel-operator found in CCS. Also, the unary prefix–operator of CCS is replaced by a binary operator for sequential composition. Although some of our techniques are related to those in [7], this simplification gives us a congruence, and in addition we approach its complete axiomatisation.

The outline of the remainder of this paper is as follows: in section 2 we present the syntax and a structured operational semantics of our calculus FC. In section 3 we present the obvious strong bisimulation equivalence between processes based on the operational semantics of section 2. This equivalence is, however, not a congruence with respect to the constructs of FC; note that strong bisimulation equivalence is preserved by almost all constructs introduced so far in process algebra (see e.g. [2]). In section 4, we provide an explicit characterization of the congruence induced by the strong bisimulation equivalence. In section 5, we offer a complete axiomatisation of this congruence.

2. Syntax and Semantics

The syntax of the calculus is as follows, where N denotes the set of process names.

L ::= nil | A | L_{1} + L_{2} | L_{1}; L_{2} | fork (L) | N
A ::= τ | a? | a!

nil is the terminated process that can perform no actions. Amongst the actions A that a process can perform are τ, the internal action, input actions of the form a?, and output actions of the form a!, where a is a channel name. Two processes that run in parallel may synchronise on complementary actions, one being an input action and the other being an output action containing the same name. The choice between two processes is written as L_{1}+L_{2}. Two processes can be sequentially composed by L_{1}; L_{2}. This has the traditional interpretation that L_{1} is evaluated first until it terminates (becomes nil) whereupon L_{2} continues. Note that FC here differs from CCS which instead of sequential composition has action prefixing. Sequential composition has greater precedence than choice.

The other main difference from CCS is the fork expression: a process is forked with fork (L). It means that a separate evaluation of L is begun such that L is made to run in parallel with the rest of the program. The fork expression itself terminates immediately after starting the separate evaluation of L. N is the call of a process, that has been named in a definition of the form N =^{def} L. In the present theory we disallow recursion in order to obtain a complete axiomatisation, and we only introduce process naming to be able to write some more appealing (nonrecursive) examples. Except for the complete axiomatisation, it is very easy to extend the results of this paper to allow recursion.

So what are the consequences of this seemingly minor change compared to CCS: having sequential composition and forking instead of action prefixing and parallel composition? There are pragmatic as well as semantic consequences. As we shall see, the semantic consequences are quite drastic. Concerning the pragmatic consequences, let us study an example. Consider the following informal requirement specification:

A ‘session’ at a computer terminal consists of an ‘initialisation’ followed by a ‘run’. The initialisation consists of a ‘setup’ phase followed by a ‘dialog’ phase. After the ‘setup’ phase, a report is sent to a paper printer which will ‘print’ the report.

This requirement specification can be directly presented in FC as follows (note that for convenience, we shall often leave out the ‘?’ when writing input actions; actions are written with small letters by convention):

Session =^{def} Initialise; run
Initialise =^{def} setup; fork (print); dialog

The point to note is that we can locally in Initialise express the parallel activation of the printer (print) after the setup phase. This local activation of a process is not directly expressible in CCS, where we would write something like:

Session =^{def} setup.(print.nil | dialog.run.nil)

We see that it is not possible to give a name (Initialise) to the pair setup and dialog: one looses abstraction. So characteristic of the fork operator is that we can start a process locally where the need arises, and this gives a possibility of naming a sequential composition that performs parallel activation as a “side effect”. Of course the above pragmatic difference between FC and CCS is just a matter of taste, and we shall in the following concentrate on the formal implications of having ‘fork ’ and ‘;’.

First, we define an operational semantics for the language of the calculus. That is, the approach taken is that of structured operational semantics as introduced in [6] and later applied to CCS as described in [4]. The difference between the semantics here and the two existing semantics of CML [9, 1] is (except for the different languages) that we let transitions be labelled with actions. Both in [9] and in [1], transitions are not labelled with actions. Action labels are important from the point of view of defining an appropriate bisimulation equivalence, since bisimulation is defined as identical action behaviour.

A labelled transition system is a triple (St, Lab, →), where St is the set of states (for example processes), Lab is the set of labels (actions performed by the processes) and →⊆ St×Lab×St is the transition relation: (st_{1}, l, st_{2}) ∈ → may be interpreted as “the state st_{1} is able to perform the action l and by doing so becomes the state st_{2}”. Typically we use the notation st_{1} →^{l} st_{2} for (st_{1}, l, st_{2}) ∈→. The transition relation thus defines the dynamic change of states as they perform actions.

The semantics of CCS is normally given in terms of a single labelled transition system where St is the set of CCS processes. In contrast to the CCS semantics, the FC semantics is divided into two layers, corresponding to two labelled transition systems. In the first layer we give semantics to processes seen in isolation (St is the set of processes). In the next layer, we give semantics to collections of processes running in parallel (St is the set of such process collections). When “running” a process, for example fork (p); q we start out with a collection consisting of that process. After the forking, we have a collection containing two processes, p and q, running in parallel. We refer to a collection of processes as a program (thus St is the set of programs).

2.1 Processes

In this section we give semantics to processes seen in isolation. We shall do this by defining a labelled transition system (L, Lab, ↪) where L is the set of processes introduced in section 2. Concerning the definition of the labels Lab, assume an infinite set of (channel) names Chan. Then Lab (the labels on process transitions) is gradually defined as follows:

Com = {a? | a ∈ Chan} ∪ {a! | a ∈ Chan}
Act = Com ∪ {τ}
Lab = Act ∪ {ϕ(p) | p ∈ L}

The set Com, ranged over by c, is the set of input-output communications that processes can perform. The set Act, ranged over by α, β, γ, …, includes in addition the τ action, and it is the set of actions that we will be able to observe in the end, when executing programs. The set Lab, ranged over by l, includes further labels of the form ϕ(p) (p ∈ L) which arise from evaluation of processes of the form fork (p). These labels will not be observable at the program layer, since at that level they will be converted into τ actions.

We now define the transition relation ↪ ⊆ L × Lab × L. Before defining this transition relation we define the predicate Stop ⊆ L. We shall use the notation Stop(p) instead of p ∈ Stop. Now Stop is defined as the least subset of L satisfying the following:

Stop(nil),

If Stop(p_{1}) and Stop(p_{2}) then Stop(p_{1} + p_{2}) and Stop(p_{1}; p_{2}), If Stop(p) and N =^{def} p then Stop(N)

The operational semantics of FC processes is then as follows.

Definition 1. (The transition relation ↪) Let ↪ be the smallest subset of L × Lab × L closed under the following rules:

(Act )  \overline{α ↪^{α} nil}

(Sum_{1} ) \frac{p_{1} ↪^{l} p′_{1}}{p_{1} + p_{2} ↪^{l} p′_{1}}  (Sum_{2} ) \frac{p_{2} ↪^{l} p′_{2}}{p_{1} + p_{2} ↪^{l} p′_{2}}

(Seq_{1} ) \frac{p_{1} ↪^{l} p′_{1}}{p_{1}; p_{2} ↪^{l} p′_{1}; p_{2}}  (Seq_{2} ) \frac{p_{2} ↪^{l} p′_{2}}{p_{1}; p_{2} ↪^{l} p′_{2}} Stop(p_{1})

(Fork ) \overline{fork (p) ↪^{ϕ(p)} nil}  (Const )  \frac{P ↪^{l} P′}{A ↪^{l} P′} A =^{def} P

The rules should be fairly simple to read. The Act -rule says that a process of the form α can perform the action α and become nil. The Sum_{1} -rule says that if p_{1} can perform l and become p′_{1}, then the sum p_{1} + p_{2} can also, and symmetrically by the Sum_{2} -rule. The sequencing rules explain how sequencing proceeds with the leftmost process until it has stopped (Seq_{1} ), whereupon the rightmost process continues (Seq_{2} ). The Fork -rule shows how the higher order labels ϕ(p) are created. When we come to the program semantics their use will be explained. Finally, the Const -rule explains how the name of a process behaves as the defining body. Let us look at an example. The process α; fork (β); γ can evaluate as follows:

α; fork (β); γ ↪^{α} nil; fork (β); γ ↪^{ϕ(β)} nil; γ ↪^{γ} nil

Note that the forked process β just becomes part of the label, and that it is not further used. When executing programs, we will make sure that the forked process is put in parallel with the “rest of the program”.

2.2 Programs

A program is a multiset of processes. A multiset can contain several copies of the same element, (in contrast to normal sets), corresponding to the fact that at a certain moment there may be several processes active with exactly the same structure. Formally, the multisets of A elements can be viewed as the elements in MS(A) = A → N. That is, a multiset of A elements is a total function from A to the natural numbers. Each A element is mapped to its number of occurrences. The union operator ∪ : (MS(A)×MS(A)) → MS(A) is defined by the equation (S_{1} ∪ S_{2})(a) = S_{1}(a) + S_{2}(a). A “finite” multiset S can be written {|p_{1}, …, p_{n}|} where the number of occurrences of a process q indicates the value S(q). As an example, {|p|}(p) = 1 and {|p|}(q) = 0 whenever p ≠ q. We let L_{p} denote the set of programs (L_{p} = MS(L)). The semantics of programs is given in terms of the labelled transition system (L_{p}, Act, ⟶), where L_{p} is defined here and Act was defined in the previous section. Thus a program can perform the actions in the set Act; recall that these were of the form a?, a! or τ. The fork actions ϕ(p) are thus not amongst program actions.

We shall now define the transition relation: ⟶ ⊆ L_{p} × Act × L_{p}. We need the auxiliary function rev : Com → Com, which for a given communication returns the complementary communication with which it can synchronise; i.e: rev(a?) = a! and rev(a!) = a?. The operational semantics of FC programs is then as follows.

Definition 2. (The transition relation ⟶) Let ⟶ be the smallest subset of L_{p} × Act × L_{p} closed under the following rules:

(Act^{\{||\}} )  \frac{p ↪^{α} p′}{\{|p|\} ⟶^{α} \{|p′|\}}  (Fork^{\{||\}} )  \frac{p ↪^{ϕ(q)} p′}{\{|p|\} ⟶^{τ} \{|p′, q|\}}

(Par_{1}^{\{||\}} ) \frac{P_{1} ⟶^{α} P′_{1}}{P_{1} ∪ P_{2} ⟶^{α} P′_{1} ∪ P_{2}}  (Par_{2}^{\{||\}} ) \frac{P_{1} ⟶^{c} P′_{1} , P_{2} ⟶^{rev(c)} P′_{2}}{P_{1} ∪ P_{2} ⟶^{τ} P′_{1} ∪ P′_{2}}

The Act^{{||}} -rule just says that if a process can perform an action, then a program containing that process can perform the action. Note that α ranges over Act, which does not contain ϕ(p) actions. The Fork^{{||}} -rule explains how a ϕ(…) action generates a τ action at program level: the forking process and the forked process will after the transition be in parallel. We have chosen to let forking generate a τ action rather than just to do a “silent” fork. The reason for this is our original goal to deal with the existing CML semantics, where forking generates a τ action. Finally, the rule Par_{1}^{{||}} explains how a “subset” of a program may perform actions on its own. Note, that we need only one such rule as ∪ is clearly commutative. The Par_{2}^{{||}} –rule shows how two distinct subsets of a program may communicate, generating a τ action.

3. Program and Process Equivalences

The purpose of this section is to define an equivalence relation ∼ ⊆ L × L between FC processes. For this purpose we shall, however, first define an equivalence relation ⌣ ⊆ L_{p} × L_{p} between FC programs.

3.1 Program Equivalence

Our notion of equivalence is based on the concept of bisimulation [4], which again is based on the idea that we only want to distinguish between two programs, if the distinction can be observed by an “observer” examining the actions that the two programs can perform. Note that in the following we shall regard the τ action just as observable as the other actions (a? and a!) in Act. We shall for example distinguish {|α; τ; β|} and {|α; β|}. This will yield a rather strict congruence (fewer programs are congruent). We have chosen to make τ observable in our first attempt, since it is the classical choice: first a strong notion of equivalence is defined (where τ is observable), and then one abstracts from τ. The formal definition of ⌣ proceeds in the standard way [4] as follows. First, we define the notion of a bisimulation.

Definition 3. (Bisimulation) A binary relation S ⊆ L_{p} × L_{p} is a bisimulation iff (P, Q) ∈ S implies, for all α ∈ Act,

(1) Whenever P ⟶^{α} P′ for some P′ then Q ⟶^{α} Q′ for some Q′ and (P′, Q′) ∈ S

(2) Whenever Q ⟶^{α} Q′ for some Q′ then P ⟶^{α} P′ for some P′ and (P′, Q′) ∈ S

We write P ⌣ Q where (P, Q) ∈ S for some bisimulation S.

As usual it may be shown that ⌣ is itself the largest bisimulation. Moreover, ⌣ is easily shown to be a congruence with respect to ∪.

3.2 Process Equivalence

In the previous section we introduced an equivalence on programs, and we stated it to be a congruence with respect to the basic operator on programs: ∪. What we are really interested in is, however, not programs, but rather processes. Processes are the terms of our language, and programs are “just” semantic objects used for giving semantics to processes. So our definite goal is to define an equivalence on processes. This equivalence must additionally be a congruence with respect to the operators on processes (+; fork ). In this section we shall come up with a process equivalence ∼, which seems rather natural and correct, but which, however, turns out not to be satisfactory due to lack of the congruence property. In the section to follow we will define an equivalence ≡ that is also a congruence, in fact the congruence induced by ∼; but the present exercise can hopefully motivate the final solution.

We shall define an equivalence relation ∼ ⊆ L × L between FC processes. Two processes p and q are said to be equivalent if (p, q) ∈∼, which is written more conveniently as p ∼ q. The idea is to say that two processes are equivalent, if they are equivalent when regarded as programs. Put differently: to see whether two processes are equivalent, “run” them (as programs) and see whether they behave the same way. Formally, we thus define ∼ as follows:

Definition 4. (Process equivalence) The relation ∼⊆ L×L is defined as:

p ∼ q ⇔ {|p|} ⌣ {|q|}

As we shall see, the fork operator causes ∼ not to be a congruence. This is rather unusual since (strong) bisimilarity is normally preserved by operators. Consider two processes that are related by ∼:

τ; α ∼ fork (α)

To see this, consider the behaviours of programs {|τ; α|} and {|fork (α)|}:

{|τ; α|}  ⟶^{τ} {|nil; α|} ⟶^{α} {|nil|}
{|fork (α)|} ⟶^{τ} {|nil, α|} ⟶^{α} {|nil, nil|}

Clearly these behaviours are bisimilar (as they are identical). Now, suppose that we put the two processes into the same context ; β. Will it then hold that τ; α; β ∼ fork (α); β? Unfortunately no!. This can be seen from the behaviours of the programs {|τ; α; β|} and {|fork (α); β|}, as illustrated by the transition trees for the two programs in Fig. 1.

Thus, ∼ is not a congruence, and we have to repair this. In the following, we introduce a process relation that is a congruence. It is even the largest congruence induced by (contained in) ∼.

4. Process Congruence

In this section we introduce a new process equivalence ≡ being strictly finer than the previously given equivalence ∼. As one of the main results of this paper we prove that ≡ is precisely the congruence induced by ∼; i.e. ≡ is

{|τ; α; β|}  {|fork (α); β|}
↓ τ  ↓ τ
{|nil; α; β|}  {|nil; β, α|}
↓ α  α ↙  ↘ β
{|nil; β|}  {|nil; β, nil|}  {|nil, α|}
↓ β  ↓ β  ↓ α
{|nil|}  {|nil, nil|}  {|nil, nil|}

Fig. 1: Different Transition Trees

preserved by all operators of FC, and is the largest such equivalence included in ∼. The fact that ∼ is not a congruence is illustrated by the previous example demonstrating the difference between the two processes τ; α and fork (α) when put into the context ; β. The difference lies essentially in the ability of the action α to be in parallel with future computation, which is here represented by the action β.

It is this ability to be in parallel with future computation that we want to capture. Future computation is what computes after the termination of the observed process. When examining the two programs {|τ; α|} and {|fork (α)|}, we cannot detect this termination: both programs perform a τ action and then an α action, and that’s it. The process τ; α, however, terminates after the second action (α), while the process fork (α) already terminates after the first action (τ), when the forking has been performed — the forked process α can then execute after this termination. We thus need to add some information that makes it possible to observe termination. This can be done by introducing a special event, π, that, when being performed, signals the successful termination of the observed process. For a given process p under observation, we shall examine {|p; π|} rather than just {|p|}.

As an example, in order to observe the difference in terms of future computations between the processes τ; α and fork (α), we “run” τ; α; π and fork (α); π as programs. That is, we examine the multisets {|τ; α; π|} and {|fork (α); π|}. Both programs can perform the trace ταπ, however only the program {|fork (α); π|} can perform the trace τπα, where the α action occurs after the π action. This may be illustrated by the transition trees for the two programs, which will be identical to the trees in Fig. 1 with π replacing β.

We are now able to give the following formal definition of the process congruence ≡:

Definition 5. (Process congruence ≡) The relation ≡ ⊆ L × L is defined as:

p ≡ q ⇔ {|p; π|} ⌣ {|q; π|}

This equivalence is similar to one of the equivalences defined in [7]; however, no axiomatisation is provided in [7].

One of the main results of this paper is that ≡ is the congruence (i.e. preserved by the operators of FC) induced by ∼. First of all, it is straightforward to show that ≡ is an equivalence relation. That it is also a congruence is stated as the following theorem.

Theorem 1. (Congruence Property) Assume processes p_{1}, p_{2} and q where p_{1} ≡ p_{2}. Then:

fork (p_{1}) ≡ fork (p_{2})
p_{1} + q ≡ p_{2} + q

q; p_{1} ≡ q; p_{2}_{p_{1}; q ≡ p_{2}; q}

Proof.

For each equation p ≡ q we shall prove {|p; π|} ⌣ {|q; π|}. Then the equation follows from definition 5. To prove {|p; π|} ⌣ {|q; π|} we define a relation S containing the pair ({|p; π|}, {|q; π|}), and then show that S is a bisimulation according to definition 3.

That ≡ is the congruence induced by ∼ is formulated as follows.

Theorem 2. (Induced Congruence Property) ≡ is the largest congruence contained in ∼.

Proof.

It is easy to show that ≡ is contained in ∼. Essentially we show that {|p|} ⌣ {|q|} whenever {|p; π|} ⌣ {|q; π|} by using definition 3. To see that ≡ is the largest such, assume that ≃ is an arbitrary congruence included in ∼. Now assume p ≃ q. As ≃ is assumed to be a congruence it follows that p; π ≃ q; π, and — as ≃ is supposed to be included in ∼ — also p; π ∼ q; π. However, then p ≡ q according to definitions 4 and 5, and it follows that ≃ is included in ≡.

5. Strong Axiomatisation

In this section we present a sound and complete axiomatisation for the process congruence ≡. The completeness of the axiomatisation is obtained in a classical manner through the use of normal forms — being process expressions with a very restricted use of the fork –operator. More precisely, the axiomatisation consists of a collection of basic equational axioms, and two expansion laws. The basic axioms achieve completeness for normal form processes, and the expansion laws (together with the basic axioms) enable arbitrary process expressions to be transformed into normal form, thus yielding completeness for the full calculus.

In more classical process calculi, expansion laws allow parallel composition to be replaced by non–determinism. In our calculus, the expansion laws will have an analogous purpose, namely that of replacing as much as possible forking with non–deterministic choice. However — as we shall see in the next section — our calculus seems to lack expressive power for suitable expansion laws to exist. To overcome this problem, we shall extend the calculus with an extra operator described below. This phenomenon is similar to the necessity of the leftmerge operator in PL in order to obtain a finite sound and complete equational axiomatisation as shown in [5].

5.1 Searching for an Expansion Law

We need expansion laws, that describe how forking may be expanded (as much as possible) into non–determinism — similar to the expansion law of CCS that expands parallel composition into non–determinism. For example, consider the following instance of the CCS expansion law:

a.nil | b.nil = a.b.nil + b.a.nil

Let us try to search for a similar expansion law for the FC fork operator. We try to expand the corresponding FC–process fork (a); b. An initial guess can be the following:

fork (a); b = a; b + b; a

However, this equation is not sound (it does not hold when replacing = with ≡), and can therefore immediately be ignored. Intuitively, the reason is that the right hand side contains no information about a’s capability to be in parallel with “the rest of the program”. In the next seemingly correct equation we try to take this into account:

fork (a); b = a; b + b; fork (a)  (1)

This equation is not sound either: the left hand side can perform a τ action as the first thing, while the right hand side can perform either an a action or a b action (note that since we want to axiomatise strong process congruence ≡, τ actions are observable). Trying to repair this problem we obtain:

fork (a); b = τ; (a; b + b; fork (a))  (2)

This equation is not sound either: the left hand side can only perform one τ action, while the right hand side can perform two (the explicitly mentioned and the one caused by fork ).

So it seems that we cannot in general expand forking! This leaves open the problem of how to establish equality between process expressions involving the fork -operator. The latter two attempts above suggest that we need a version of fork which is instantaneous without the initial internal transition caused by fork . Put alternatively, we lack the ability to express that something has been forked, with the initial τ action already having occurred previously. To gain this expressivity, we add a new instantaneous forking operator to our calculus. We call this operator forked , and we write forked (p) to mean ‘fork p without a τ’. One can regard this operator alternatively by reading forked (p) as ‘p has been forked’, or shorter: ‘forked p’. The latter reading suggests the following relationship between fork and forked :

fork (p) = τ; forked (p)

Let us now try to write new versions of Eq. (1) and Eq. (2), that contain the forked operator in appropriate positions. These equations can be shown sound on basis of the formal definition we give of the forked operator in the following section. The equations are:

forked (a); b = a; b + b; forked (a)
fork (a); b = τ; (a; b + b; forked (a))

However, it remains to settle whether it is possible to find an operator forked satisfying the above equations. In the next section we give an affirmative answer by providing an operational semantics for this new operator.

5.2 Adding the ‘Forked’-operator

The syntax is extended with the forked alternative:

L^{Φ} ::= nil | A | L^{Φ}_{1} + L^{Φ}_{2} | L^{Φ}_{1}; L^{Φ}_{2} | fork (L^{Φ}) | forked (L^{Φ}) | N

The extended calculus is called FC^{Φ}. The set Lab of actions that a process can perform is extended accordingly:

Lab = Act ∪ {ϕ(p) | p ∈ L^{Φ}} ∪ {Φ(p) | p ∈ L^{Φ}}

The new semantics of processes and programs is obtained by adding the following rules:

(Forked )

forked (p) ↪^{Φ(p)} nil

(Forked^{{||}} ) \frac{p ↪^{Φ(q)} p′, \{|p′, q|\} ⟶^{α} R}{\{|p|\} ⟶^{α} R}

The definition of Stop is unchanged for the extended calculus. That is: ¬Stop(forked (p)) for any p. The previous semantic definitions from section 2 — the sets Com and Act (however not Lab), and the operational semantic rules for the original operators — carry over unchanged to the extended calculus. Likewise do the definitions of the program equivalence ⌣ and the process equivalences ∼ and ≡ from sections 3 and 4. The proofs of the following properties also carry over: (1) ⌣ is a congruence with respect to ∪, (2) ∼ is an equivalence (but not a congruence, as before), (3) ≡ is an equivalence.

The congruence property of ≡ does, however, not apply to the extended calculus. The reason for this can be illustrated by the following example. It is easily shown that forked (nil) ≡ nil. Now, consider the two processes forked (nil) + α and nil + α. In order for ≡ to be a congruence, these two processes should be equivalent (show the same behaviour). They are, however, not equivalent since forked (nil) + α can avoid to perform the α action while nil + α must perform the α action.

We choose to loosen the requirement that ≡ is a congruence. The congruence property holds for all the operators, except for the choice operator, hence it becomes conditioned (even a conditioned congruence can be useful from a practical point of view). Let Active(q), for any process q ∈ L^{Φ}, mean that q can perform an action. That is: Active(q) =^{def} ∃α ∈ Act, P ∈ L_{p}^{Φ} · {|q|} ⟶^{α} P. Then the loosened congruence property can be stated as follows: assume for two processes p_{1}, p_{2} that p_{1} ≡ p_{2}. Then for any process q:

p_{1} + q ≡ p_{2} + q provided Active(q) ⇒ (Stop(p_{1}) ⇔ Stop(p_{2}))

The condition is motivated by our previous example, where q is α, and p_{1} is forked (nil) and p_{2} is nil, thus the condition is not satisfied in this case. Note that Stop(forked (nil)) does not hold according the definition of Stop which is unchanged for the extended calculus.

5.3 Basic Axioms and Inference Rules

The axiom system AS consists of a set AX of equational axiom schemes, two expansion laws E_{1} and E_{2}, using normal forms, and a set I of inference rules, that represents the fact that ≡ is an equivalence and (nearly) a congruence. In this section we shall present the basic equational axioms AX.

Definition 6. (Basic axiom system AX) The basic axiom system AX consists of the axioms:

fork (p) = τ; forked (p)
forked (α; forked (p) + q) = forked (α; p + q)
forked (nil) = nil
\begin{aligned}p; nil & = & p \\ nil; p & = & p \\ (p; q); r & = & p; (q; r) \\ (p + q); r & =^{∗} & p; r + q; r \end{aligned}  \begin{aligned}p + (q + r) & = & (p + q) + r \\ p + q & = & q + p \\ p + nil & = & p \\ p + p & = & p \end{aligned}

The =^{∗} equality holds when ¬Stop(p) and ¬Stop(q).

5.4 Normal Forms

Our way to completeness is classical in that it is based on normal forms. For processes in normal form, the basic axioms AX will be shown to be complete, and we shall in the next section present two expansion laws that will enable any process term to be transformed into a provable equivalent normal form process term.

Processes in normal form contain no applications of the fork operator, and applications of the forked operator occur last. One intuition behind this is that when observing a process we cannot observe the individual forkings; and as long as the original process has not terminated, we cannot observe which actions come from it and which actions come from the forked processes. We can, however, in contexts observe when the process terminates and what at that time has been forked. In principle a process is brought into normal form by first replacing applications fork (p) by τ; forked (p), and then by moving the applications of forked as much as possible “to the right”. Note that we must deal with the forked operator in normal forms, since we must keep track of what is potentially in parallel with future computation.

As an example, consider the process fork (α); β. First, we can replace fork (α) with τ; forked (α), obtaining τ; forked (α); β. Then the process forked (α); β, is brought into the form α; β + β; forked (α). As result we obtain τ; (α; β + β; forked (α)). We see that there is no application of the fork operator, and that the application of the forked operator occurs last. Let us now formally introduce normal forms:

Definition 7. (Normal form) A process p is in normal form, if it is a term in N, where:

N ::= ∑_{i} α_{i}; N_{i} + ∑_{j} β_{j}; forked (B_{j})  and B ::= ∑_{k} β_{k}; B_{k}

A process p is in simple normal form, if it is a term in B.

Note that simple normal forms contain no applications of the forked operator. That is, once we have reached the termination of a process (second alternative in the definition of N), what remains has all been forked, and to express this we only need one application of the forked operator. That is, we do not care about any “further” forking.

5.5 Expansion Laws

The two expansion laws to be presented in this section will enable any process term to be transformed into normal form. To motivate the two expansion laws consider the case of a sequential composition of two process terms N and M already in normal form. Now assume N is of the following form:

N = ∑_{i} α_{i}; N_{i} + ∑_{j} β_{j}; forked (B_{j})

Then, when using the laws for distributing ; over + and laws of associativity for ;, we obtain:

⊢ N; M = ∑_{i} α_{i}; (N_{i}; M) + ∑_{j} β_{j}; (forked (B_{j}); M)

To enable this term to be transformed into a normal form we introduce the following expansion law:

Definition 8. (Expansion law E_{1})

Let: A = ∑_{i} α_{i}; A_{i}, N = ∑_{j} β_{j}; N_{j}, L = ∑_{k} γ_{k}; forked (C_{k}) and M = N + L where M is not nil. Then:

forked (A); M = p + q + r + s + t

where \{ \begin{matrix}p & = & ∑_{i} α_{i}; forked (A_{i}); M \\ q & = & ∑_{j} β_{j}; forked (A); N_{j} \\ r & = & ∑_{k} γ_{k}; forked (A); forked (C_{k}) \\ s & = & ∑_{α_{i}=rev(β_{j})} τ; forked (A_{i}); N_{j} \\ t & = & ∑_{α_{i}=rev(γ_{k})} τ; forked (A_{i}); forked (C_{k}) \end{matrix}

To be able to inductively transform forked (A); M into normal form, we see in definition 8 that we need to be able to handle terms of the form forked (A); forked (B). This motivates the second expansion law below:

Definition 9. (Expansion law E_{2})

Let: A = ∑_{i} α_{i}; A_{i} and B = ∑_{j} β_{j}; B_{j}. Then:

forked (A); forked (B) = forked (p + q + r)

where \{ \begin{matrix}p & = & ∑_{i} α_{i}; forked (A_{i}); forked (B) \\ q & = & ∑_{j} β_{j}; forked (A); forked (B_{j}) \\ r & = & ∑_{α_{i}=rev(β_{j})} τ; forked (A_{i}); forked (B_{j}) \end{matrix}

The basic axioms AX, the two expansion laws E_{1} and E_{2} and the inference rules I constitute our proof system AS, which we shall show to be (limited) complete. We write ⊢ p = q if the equivalence of p and q can be entailed within the proof system AS.

5.6 Soundness and Limited Completeness

In this section we state, that the axiom system AS is sound and complete with respect to strong process congruence. That is, ⊢ p = q ⇔ p ≡ q. The basic technique for showing completeness is classical; i.e. first to prove that each process term in the original forked -free calculus of section 2 is equivalent to a term in normal form, and then to prove completeness for normal forms. The fact that the completeness result only will apply to forked -free processes makes it “limited”. First we state the soundness result.

Theorem 3. (AS is sound) For all p, q ∈ L^{Φ}, whenever ⊢ p = q then p ≡ q.

Note that the result holds for all p, q in FC^{Φ}. The following proposition states that there exists a normal form for every process term in FC. Note that there does not exist a normal form for every process term in FC^{Φ}; as an example, the process forked (α) has no normal form.

Proposition 1. (Normal forms for FC) For all p ∈ L, there exists a normal form N such that: ⊢ p = N.

The next step is to show completeness for normal forms.

Proposition 2. (Normal form completeness) For all normal forms M, N ∈ L^{Φ}, whenever M ≡ N then ⊢ M = N.

These two propositions can be used to prove the (limited) completeness result. The completeness result is limited in the sense that it only holds for process terms of FC (and not FC^{Φ}).

Theorem 4. (AS is limited Complete) For all p, q ∈ L, whenever p ≡ q then ⊢ p = q.

6. Conclusion and Future Work

In this paper we have identified and axiomatised a congruence ≡ between FC–processes. Also, it has been demonstrated that ≡ is the congruence induced by a natural strong bisimulation equivalence ∼.

In practice, however, both ∼ and ≡ are too fine, as they are sensitive to internal computations. To obtain a more suitable equivalence, we may consider the weak bisimulation equivalence ≈ between processes, in which we abstract away from τ–transitions. Doing this, we will observe the same phenomena as we did for the strong equivalence: ≈ is not preserved by sequential composition. Applying the technique of this paper — i.e. defining p ≅ q iff p; π ≈ q; π — we obtain the coarsest equivalence contained in ≈ which is also preserved by ; (and fork ). However, ≅ will not quite be a congruence as it will not be preserved by the choice–operator. This is a classical problem [4], and we conjecture that the classical techniques for overcoming this problem will apply to ≅. Also, we conjecture that the congruence obtained in this manner will be completely axiomatized by the axiomatization AS presented in this paper augmented with the classical τ–laws [4].

As immediate future work we intend to investigate the FC–calculus extended with primitives for dynamic channel creation and communication of channels. Further future work includes design of a (logical) specification language for specifying properties of FC programs. It should be shown that the designed specification language is adequate with respect to a suitable process–equivalence (for example the one defined here); i.e. two processes enjoy the same properties of the specification language precisely when they are behaviourally equivalent. One may consider a modification of the specification language which is defined in [3] for CCS, usually referred to as Hennessy-Milner logic.

A more long–term ambition is to extend our work to the full language of CML.

References

[1] Berry, D., Milner, R., and Turner, D.N. 1992. A Semantics for ML Concurrency Primitives. In Proceedings of the 19th ACM Symphosium on Principles of Programming Languages.

[2] Groote, J. F. and Vaandrager, F. W. 1989. Structured Operational Semantics and Bisimulation as a Congruence. LNCS 372.

[3] Hennessy, M. and Milner, R. 1985. Algebraic Laws for Nondeterminism and Concurrency. Journal of ACM 32, 1 (January).

[4] Milner, R. 1989. Communication and Concurrency. International Series in Computer Science. Prentice Hall.

[5] Moller, F. 1990. The Importance of the Left Merge Operator in Process Algebra. LNCS 443.

[6] Plotkin, G. 1981. A Structural Approach to Operational Semantics. FN 19, DAIMI, Aarhus University, Denmark.

[7] Prasad, S., Giacalone, A., and Mishra, P. 1990. Operational and Algebraic Semantics for Facile. In Proceedings of ICALP90, LNCS 443.

[8] Reppy, J. H. 1991. CML: A Higher-order Concurrent Language. In ACM SIG-PLAN’91 Conference on Programming Language Design and Implementation (SIGPLAN Notices 26(6)).

[9] Reppy, J.H. 1991. An Operational Semantics of First–class Synchronous Operations. Tech. Report TR 91–1232, Cornell University, Department of Computer Science.