[10]. External interactions are also modeled as joint actions, without committing to ... In principle, a joint action language can be used as a wide spec- trum language from ... of concurrent systems by a layered introduction of properties. DisCo is an ... ed programs. 1. .... enabled and all other required processes are also free.
1 of 7
Action System Approach to the Specification and Design of Distributed Systems R. Kurki-Suonio and H.-M. Järvinen Tampere University of Technology Software Systems Laboratory Box 527, SF-33101 Tampere, Finland
Abstract The notion of joint actions provides a paradigm that allows the specification and design of distributed systems to focus on cooperative events rather than on the behavior of individual processes. For concurrency this introduces an abstraction that is independent of process structuring and of communication mechanisms. For the designer this means replacing the conventional process-oriented view by an action-oriented one, which has a profound effect on thinking about a system and on the design process. The approach is especially suited for formal derivation of concurrent systems by a layered introduction of properties. DisCo is an executable specification and design language based on joint actions. This paper introduces the basic principles of the joint action approach, together with the main capabilities of DisCo for supporting modularity and the derivation of distributed programs. 1. Introduction to Specification and Design by Joint Actions The notion of joint actions [7] provides a novel approach to the specification and design of distributed systems. The key idea is that the traditional process-oriented viewpoint is replaced by an action-oriented one. Instead of describing the behavior of the individual processes the design focuses on the joint effects that the processes are to accomplish together. From an execution model point of view actions are atomic events. Different interleavings are possible by the nondeterminism that is inherent in the selection of actions for execution. All cooperation is implicit in the actions and their enabling conditions, and no process communication primitives are therefore needed. This means that the level of abstraction is raised to independence of process structure and communication mechanisms. This makes joint actions a suitable basis for a specification language. Joint action specifications are executable (simulatable), and their simple execution model allows proofs either
Permission to copy without fee all or part of this material is granted provided that the copies are not made or distributed for direct commercial advantage, the ACM copyright notice and the title of the publication and its date appear, and notice is given that copying is by permission of the Association for Computing Machinery. To copy otherwise, or to republish, requires a fee and/or specific permission.
in temporal logic [24, 25] or in the logic developed for Unity [10]. External interactions are also modeled as joint actions, without committing to specific communication events between the system and its environment. This means that a joint action system is a closed system containing also a model of its environment. This has an effect on modularity, as will be seen below. For the design process joint action systems provide a framework for stepwise derivation. The initial action system with which this process starts should be simple. Fortunately, the natural ways of structuring action systems support a layered introduction of properties. Bias towards machine architecture or communication primitives does not belong to the initial system, even if the eventual answers to such questions were known from the beginning. No harm is caused at this level by wasteful computations allowed by nondeterminism. Furthermore, the granularity of atomic actions can be coarser than would be feasible in a distributed implementation. Together with independence of communication mechanisms this helps in the avoidance of subtle timing errors. The initial action system is the first baseline for the design process. Being amenable to both formal analysis and experimentation (by simulation and animation), it can be subjected to extensive verification and validation. From this stage the design proceeds by transformations that • refine the atomicity of actions, • restrict the nondeterminism by specific implementation strategies, • enrich the system with additional properties, • refine data representations, and • tailor the system towards specific distributed architectures, communication mechanisms and decisions of distribution. In principle, a joint action language can be used as a wide spectrum language from specification to implementation. Direct implementation as a programming language seems feasible only under restrictions that are too severe for a specification language. However, one of the goals in transforming an action system is to tailor it towards available implementation mechanisms. A compiler can be provided to produce the final program, once the required conditions for direct implementability have been achieved.
2 of 7 The role of the language and its environment is essential in supporting the correct use of transformations. Some of these can be performed by the computer, but their selection requires insight that is best provided by a human designer. Evidence from case studies [23] indicates a need for also non-standard transformations with specialized correctness proofs. The notion of action systems was introduced in [3]. The associated design and proof methods were investigated further in [4, 21, 7, 2]. Its use as a basis for a practical design method was proposed in [5], and realistic case studies for its evaluation have been reported in [23, 22]. These reports also contain the first steps towards developing the ideas into a specification and design language. Another action-oriented approach to the design of parallel systems is Unity, developed along with an associated proof system by K. M. Chandy and J. Misra [10]. The relationship between the two approaches is very close. In fact, under a specific fairness assumption called action justice in [6], any Unity program can be directly understood as an action system. Therefore, the methodologies of [10] can be used also with action systems. The problem of ensuring the validity of the fairness assumption in a distributed execution environment was addressed in [6]. This paper reports on an experimental specification and design language, DisCo (for Distributed Cooperation), that is based on action systems. Emphasis is on its support for modularity and program derivation. An introduction to joint action systems is first given in Section 2. The basic entities of DisCo are then described in Section 3. The remaining sections are devoted to issues of modularity and transformations. 2. Action Systems as a Paradigm of Computation Any execution model of computing involves a state (memory, registers, variables) and actions that modify this state (instructions, statements, transitions). Conventionally the state is partitioned into a data part (variables) and a control part (instruction counter). The von Neumann paradigm and structured programming are based on this partitioning and on sequential control. The same basic paradigm is preserved in the object-oriented approach, where control flow relations between objects are determined by messages instead of control transfer. Concurrency and distributed systems have changed the situation by introducing more complexity into the control part, which has become the Cartesian product of the control parts of the component processes. In addition, deciding on process structure and distribution involves a tradeoff in implementing state components either in the data part or in the control part, and the von Neumann paradigm is clearly inadequate for dealing with such issues of design. Action systems provide a model of computation with no such a priori partitioning of the state. State transitions are given as an unordered set of actions that have the form of guarded commands. Any of them can be executed whenever it is enabled, i.e., its guard is true. The commands themselves are assumed to be deterministic and to terminate in finite time. An execution of a system terminates if a state is reached where none of the actions is enabled; otherwise the execution is infinite.
In principle, an action system behaves like a single iteration statement in Dijkstra's guarded command language [12], or a production system like an OPS5 program [14]. There is a difference, however, in how this paradigm is used. In our case nondeterminism reflects an interleaving model of concurrency. Therefore, there is no need for conflict resolution, as in production systems, for determining which of the enabled commands to execute next. With actions as atomic transitions, the execution model of [24] and temporal logic can be directly used for reasoning. With a specific fairness assumption the logic of Unity [10] can also be used. Atomicity of actions means that only one of them is thought to be in execution at a time. When it terminates, any of the actions that are then enabled can be chosen next. No concurrency of actions exists in this sequential execution model, which is a logical model for reasoning. Its relationship with realistic concurrent implementations is discussed below. An implementation of an action system on a distributed architecture involves an allocation of state components to processes. In [3, 7] the state of an action system was given explicitly in terms of variables allocated to processes. Given such an allocation, the state components used in the command part of an action determine the processes that are needed to participate in it. An action can then be thought of as being executed jointly by these processes. Hence the name joint actions. For a singleton set of participating processes an action is called private by that process. The guard of an action is called local if its evaluation involves the participating processes only; otherwise it is global. One of the simplifying properties to be achieved by transformations is that all guards are local (in the intended allocation), in which case the decision to execute an action can be based on communication between the participating processes only. A methodology for such transformations was the topic of [3]. Considered as a process communication mechanism joint actions are a generalization of the synchronized rendezvous of languages like CSP [18, 19] and Ada [1]. Notice that in our case the number of participating processes is not restricted to two, and guards can be arbitrary boolean expressions. There is also a more fundamental difference. In process-oriented languages a rendezvous is just another operation in which processes can get involved, while in our case joint actions are the active units of execution, and processes behave like passive resources. This distinguishes joint action systems from languages that are based on a multi-process handshake mechanism, like Raddle [15] and the shared action systems of [26]. During design, an action system is to be transformed into a form that reflects the properties of an implementation environment. For instance, if asynchronous messages are to be used for communication, the communication channels can be modeled as auxiliary state components that are used in a specific manner. Under certain conditions joint action systems are directly amenable for implementation. CSP implementation of two-process actions was studied in [3], and broadcasting network implementation of more general actions in [7]. A realistic execution model for an implementation of joint actions is a concurrent model where it is possible for several ac-
3 of 7 tions to be in progress simultaneously. Independent of how the guards are evaluated, synchronized handshake by the participating processes is required to start an action. After becoming committed to one by a handshake, a process cannot start another, until it has finished its part in the former. Although no process can participate in more than one action at a time, several actions involving disjoint sets of them can proceed in parallel in the concurrent model. Depending on the length of time that different processes are needed in an action, they can be released at different times. When released, a process can immediately start participation in another action, if one is enabled and all other required processes are also free. For details of the relationship between the two execution models, see [6, 7]. The sequential model is intended for reasoning, even if the implementation follows the more realistic concurrent model. The two models differ only in their fairness notions [2]. In practical design situations these differences do not seem very important, since mere fairness properties are insufficient, anyway, and more stringent real-time constraints are needed. For simplicity, fairness issues are ignored in this paper. 3. Basic Entities in DisCo In DisCo the global state of an action system is partitioned into objects with local states. From an implementation point of view objects correspond to processes, data items, and physical resources. The allocation of objects to such implementation entities is not dealt with in the language. An object can be characterized as a data structure (local state) together with the capability to participate in certain actions. In order to deal with collections of similar objects, the notion of object types or classes is adopted. This is significant also for the reusability of specifications. No explicit naming facility is provided for individual objects; their selection is always by class and properties of local state. The behavior of a DisCo system is determined by a set of actions. These are, in fact, action schemes in the sense that the participating objects are specified only by their classes. Action guards can also be global; reference to other than the participating objects is by existential or universal quantification. As an example, consider a situation with a number of Client and Server objects, such that each available Server is able to serve any Client that is requesting service. This can be expressed by an action Service of the form action Service by C: Client, S: Server is when C.requesting ∧ S.free do ... where the first line gives the participants, the guard is on the second line, and the body (or command part) of the action is omitted. Comparing to [7, 10], unique identification of individual objects (by indexing, for instance) is avoided by binding formal names to actual participants. If all Servers, for instance, are interchangeable, they need no identification attribute. Notice that dynamic identification can easily be given when needed. For instance, if a longer-term association is required between a Client C and a Server S, this can be achieved by a data attribute S.c to
which (a pointer to) C is assigned in Service. An action of the form action Continued_Service by C: Client, S: Server is when ... ∧ S.c = C do ... would then assume that such an association has been established. Analogously to selecting the participating objects of an action, data values can also be selected by an action. Such parameters are nondeterministically chosen values that can be restricted by the guard. This provides an implicit mechanism for the participating objects to "negotiate" values as in LOTOS [8]. A DisCo program consists of two parts, called a system and a creation. The system part gives the class definitions and the actions, together with invariants that express safety properties. Functions and relations between objects can also be defined in this part to be able to express global properties in invariants and action guards. The creation part creates all objects and initializes them. The two parts are given separately in order to allow the execution of the same system part for different sets of objects. Once all objects have been created, actions of the system part can start execution. These affect the global state of the system but cannot change its structure, i.e., destroy objects or create new ones. There is no need for this, either, since being destroyed can be understood as another state of an object, and new objects can always be taken in use if an infinite number of them have been created initially. Notice that infinite collections of similar objects cause no obstacle for the implementation of DisCo. 4. Structuring of Objects Since action systems have no explicit control flow, the modularity of structured programming is not applicable. (For an illuminating discussion of the separation of modularity and control flow, see [10].) It should also be noticed that joint actions are only an execution model without any inherent structuring of the system. Attempts to use them in their bare form tend to lead to unstructured introduction of boolean flags and enumeration variables. The purpose of classes and objects is to impose structuring on the global state of a DisCo system. The local state of an object is partitioned further into two parts, a finite-state part and a data part. The former is given as a hierarchical set of states, and it is an analogue of the nested control structure of a process. The latter consists of variables and constants that are associated with the states of the finite-state part. There is no theoretical reason for including some components of the local state in the finite-state part. In principle, the finitestate part can always be omitted, in which case objects are just ordinary records. Notice that the structuring of an object is independent of its eventual implementation-oriented role. In particular, finite-state parts need not be mapped to the control part in a conventional implementation. The finite-state part of an object provides an approximation of the local state that is representable as a statechart-like diagram [16]. Actions that change the finite-state part are given as tran-
4 of 7 sitions in the diagram. This leads to a structured way to look at an action system from the viewpoint of individual objects. The advantages of nested control structures and associated variable declarations are thereby achieved without committing to modules with executable code and specific entry and exit points. As an example consider a class of Interface objects that connect environment objects to some system. The following diagram sketches the possible structure of such objects: engaged *live *silent dead responding *free *active
associated when engaged, and the messages associated with the states responding and requesting. A variable, immediate_data, is associated with the state live. This is assumed to represent such information, to be collected by an Interface object, that can be used to respond to the User without consulting the system. When comparing to statecharts [16] and their use in STATEMATE*[17], the following differences may be noted. Our statechart-like diagrams are associated with classes, not single objects, which enhances reusability. Our diagrams also apply to all classes, independently of their roles in the system. Furthermore, they are not given explicitly by the user, but are derived from class definitions and actions. Notice that an action, which is atomic in the execution model, may involve transitions in all participating objects. In STATEMATE, individual transitions are given separately, but they can trigger further transitions, thus forming larger atomic events that would correspond to our actions. 5. Superposition and Union
*idle finished requesting
Each Interface is either free or engaged, depending of whether it is currently reserved to serve an environment object or not. The prefix (*) indicates an initial or default state. When engaged, an Interface has two faces, one towards the environment, the other towards the system. This is represented by the two parallel statechart components. Towards the environment an Interface is either live or dead, depending on whether it can still communicate with it or not. Similarly, it is either active or finished towards the system, depending on whether it can still affect it or not. When live, an Interface is responding when it has a response available for the user; otherwise it is silent. Similarly, when active, it is requesting when it needs to communicate with the system, and is idle otherwise. As a class definition this hierarchical structure could be given as follows: class Interface is state *free, engaged(u: User); extend engaged by state *live, dead; extend live by immediate_data: some_type; state *silent, responding(response: message); end live; state *active, finished; extend active by state *idle, requesting(request: message); end active; end engaged; end Interface; In addition to the finite-state part, the data part is also given here. Components that remain constant in a state are given in a parameter notation. Such are the User with which an Interface is
Superimposition, or superposition, is a design method that has proved useful especially in the development of distributed systems. In principle it is a layered approach where, starting from an initial solution satisfying some basic requirements, further properties are imposed without violating those that have already been established. An early use of the technique and the term was in [13]. Recently superposition has been proposed as a control structure for concurrent and distributed programming languages [20, 9]. In [10] it was introduced as one of the main facilities for designing Unity programs in a modular fashion. In connection with action systems the technique has been successfully used in [3, 4, 21, 22]. In a conventional process-oriented description superposition easily involves modifications that are scattered all over the system. In an action-oriented description the corresponding modifications are local, which makes superposition an especially natural construction for this approach. Although the key idea in superposition is that a new layer should not destroy properties that have already been established, there is no general agreement on its precise requirements. There are at least three possibilities: (i) a superposition construct should guarantee that all safety and liveness properties are preserved [3, 10]; (ii) preservation of safety properties should be guaranteed, but liveness properties may require separate proofs [9]; (iii) preservation of the specified safety and liveness properties needs a separate proof [20]. In view of our experience that safety properties are the more important ones in practice, and that nondeterminism often needs to be restricted during design, we have adopted alternative (ii). As there clearly exists a need for more liberal transformations also, option (iii) is separately available, but is not called superposition. In DisCo superposition is allowed to extend the global state both by extending old objects (classes) with new components and by adding new objects (classes). For updating the new state components, old actions can be extended and new actions can be added. The guards of old actions can also be strengthened. *STATEMATE is a registered trademark of i-Logix, Inc.
5 of 7 What is essential is that projecting an execution of the resulting system to the original objects should always yield an execution of the original system. This can be guaranteed by the construction otherwise, but if guards are strengthened, separate proofs are needed to show that no computation is caused to terminate prematurely. As an example, consider a sequence of Processor objects, each initialized with an integer x and with knowledge of the Processors to their left and right, if any: class Processor(left, right: Processor) is x: integer; end Processor; The task of the Processors is to sort the integers x into a non-decreasing order from left to right. An initial system, Basic_Solution, could contain the above class definition together with a simple Compare action: action Compare by L, R : Processor is when L.right = R do if L.x > R.x then L.x := R.x || R.x := L.x; end if; end Compare; This action is always enabled for any two neighboring Processors; its effect is to compare their numbers x and to exchange these if the order was not correct. (The parallel combinator || indicates that both right-hand sides are evaluated before the assignments.) Obviously, Basic_Solution imposes no strategy on when to execute the comparisons, and it does not terminate. Suppose that we wish each Processor to communicate alternatingly with its left and right neighbor (except for the leftmost and rightmost ones, which have only one neighbor). This property can be superposed by importing the Basic_Solution and modifying its definitions as follows: system First_Extension with Basic_Solution is extend Processor by state looking_left, looking_right; end Processor; extended action Compare is when ... L.looking_right ∧ R.looking_left do ... if L.left ≠ null then → L.looking_left; end if; if R.right ≠ null then → R.looking_right; end if; end Compare; end First_Extension; The Processor objects are extended here with states that indicate the directions of communication. The guard of Compare is strengthened with the aid of these states, and the body is extended to update the directions of communication. Ellipses stand for parts taken from the imported system, and arrows (→) express transitions in finite-state parts. By importing two or more systems we get their union, i.e., a system that consists of all classes and actions of the component systems. The resulting system can then be transformed further by
superposition. Union is especially useful when the component systems share some classes. This is the case when these systems have themselves imported such classes from a common source. Union is one of the modularity notions discussed in [10]. 6. Inheritance While superposition is suited for top-down design, inheritance is a closely related bottom-up technique for modular construction of reusable components. Language constructs for inheritance have been developed in connection with object-oriented programming [11]. Inheriting a previously defined class in DisCo means that, in addition to the specific attributes of the new class, all the attributes of the inherited class are also assumed, including the capability to participate in the actions defined for it. Inheritance of several classes, and multiple inheritance of the same class are also permitted. Technically inheritance can be described as a form of superposition. Inheriting a class is, in fact, equivalent to superposition with predefined extensions that have been formulated in terms of a class. In conventional, control flow-oriented modularity, a module and its interface are defined independently of those modules that can interact with it. In joint action systems the situation is different, as there are no interactions outside joint actions, and these cannot be defined without access to attributes of all interacting objects. This leads to a notion of modularity where using a module requires that this capability has been explicitly inherited from the module itself. As an example consider the definition of a simple reliable Channel between a Sender and a Receiver, with capacity to transmit one message at a time: system Simple_Channel is class Channel is state free, busy(m: message); end Channel; class Sender(to: Channel) is state preparing, giving(m: message): end Sender; class Receiver(from: Channel) is state ready, processing(m: message); end Receiver; action Send by s: Sender, c: Channel is when s.to = c ∧ s.giving ∧ c.free do → c.busy(s.m), s.preparing; end Send; action Receive by r: Receiver, c: Channel is when r.from = c ∧ r.ready ∧ c.busy do → r.processing(c.m), c.free; end Receive; end Simple_Channel; In addition to Channel objects, this module specifies the attributes that are required for their use. For instance, if objects of class Actual_Sender are to act as Senders to objects of class Actual_Receiver, the attributes of Sender and Receiver must be inherited by them as follows:
6 of 7 class Actual_Sender(to: Channel) is inherit Sender(to); -- other attributes of Actual_Sender end Actual_Sender; class Actual_Receiver(from: Channel) is inherit Receiver(from); -- other attributes of Actual_Receiver end Actual_Receiver; In this example inheritance creates parallel components to the outermost states of the new classes, which corresponds to the conventional idea of inheritance. The state structure of DisCo also allows inheritance to internal substates. An object of the inheriting class then belongs to the inherited class only when it is in the substate in question. This provides a flexible way to regulate the behavior of an object as an object of the inherited class. 7. Further Transformations It is obvious that the above possibilities for modifying action systems and to use existing components for their construction are not sufficient in practice. In particular, there is a need to refine systems by splitting actions into smaller ones [2, 23]. General transformations can be found to accomplish this, but the possibility for specific transformations with individual proofs also remains. For such purposes the mechanism of importing and modifying a system can be used in DisCo also in a more liberal way where the restrictions of superposition are removed. As a simple example, consider an action by two objects X and Y that has been massaged into a form reflecting an implementation where X first prepares a message to Y, and this message is subsequently processed by Y. Utilizing an asynchronous channel module, this action can be replaced by two separate actions, of which one is private for X, the other is private for Y, and actions Send and Receive of the channel module are executed between these. 8. Concluding Remarks DisCo language and its environment are being developed in a project which is part of the FINSOFT programme of the Technology Development Centre of Finland. The first version of DisCo is purely experimental; more experience is needed to decide on a balanced set of properties for this kind of a language. For action bodies it seems that assignment statements (with conditions) are sufficient for the purposes we have in mind. The importance of conventional data structures is decreased by the possibility of treating their components as independent objects. On the other hand, there is an obvious need to support more abstract notions like sets, mappings and relations. An interpreter and animation environment are being implemented on a workstation computer. The basic notions of action systems, i.e., objects and actions, provide a generic collection of entities to be visualized. With statechart-like structuring it seems that application-specific tailoring of animations can be kept to a minimum. When several layers of refinements exist, the user will be given the freedom to choose the level of abstraction in monitoring the system. The approach presented in this paper seems also suitable for documenting a system in terms of the transformations that have
been used in its derivation. It should be noticed, however, that there is not much experience in managing such derivations, and further work is required in this respect. More understanding is also needed of the transformations to be used in the design process, and of the associated simulation mappings between consecutive design layers. Other areas for further research are computer-supported correctness proofs of action system transformations, and automatic compilation of (restricted) action systems. References [1] [2] [3]
[4]
[5] [6]
[7]
[8]
[9]
[10] [11] [12] [13] [14]
[15]
[16] [17]
[18] [19] [20]
Ada Programming Language. ANSI/MIL-STD-1815A-1983. R.J.R. Back, Refining atomicity in parallel algorithms. Reports in Computer Science 57, Åbo Akademi 1988. R.J.R. Back and R. Kurki-Suonio, Decentralization of process nets with a centralized control. To appear in Distributed Computing. An earlier version in Proc. 2nd ACM SIGACT-SIGOPS Symposium on Principles of Distributed Computing, Montreal, Canada, Aug. 1983, 131-142. R.J.R. Back and R. Kurki-Suonio, A case study in constructing distributed algorithms: distributed exchange sort. In Proc. Winter School on Theoretical Computer Science, Lammi, Finland, Jan. 1984, Finnish Society of Information Processing Science, 1-33. R.J.R. Back and R. Kurki-Suonio, A new paradigm for the design of concurrent systems. Ada Letters VII, 6 (Fall 1987), 110-112. R.J.R. Back and R. Kurki-Suonio, Serializability in distributed systems with handshaking. In Proc. ICALP 88, Automata, Languages and Programming (Ed. T. Lepistö and A. Salomaa), LNCS 317, Springer-Verlag 1988, 52-66. R.J.R. Back and R. Kurki-Suonio, Distributed cooperation with action systems. ACM Trans. Programming Languages and Systems 10, 4 (Oct. 1988), 513-554. T. Bolognesi and E. Brinksma, Introduction to the ISO specification language LOTOS. Computer Networks and ISDN Systems 14, (1987), 25-59. L. Bougé and N. Francez, A compositional approach to superimposition. In Proc. 15th ACM Symposium on Principles of Programming Languages, San Diego, California, Jan. 1988, 240249. K.M. Chandy and J. Misra, Parallel Program Design: A Foundation. Addison-Wesley 1988. O.-J. Dahl, B. Myhrhaug and K. Nygaard, Simula 67 common base language. Report S-22, Norwegian Computer Center, Oslo, 1970. E.W. Dijkstra, A Discipline of Programming, Prentice-Hall 1976. E.W. Dijkstra and C.S. Scholten, Termination detection for diffusing computations. Information Processing Letters 11, 1 (Aug. 1980), 1-4. C. Forgy and M.C. Dermot, OPS, a domain independent production system language. In Proc. Fifth International Joint Conference on Artificial Intelligence, 1977, 933-939. I.R. Forman, On the design of large distributed systems. Microelectronics and Computer Technology Corporation, Report STP-098-86, Jan. 1987. D. Harel, Statecharts: a visual formalism for complex systems. Science of Computer Programming 8, 1987, 231-274. D. Harel et al., STATEMATE: A working environment for the development of complex reactive systems. In Proc. 11th International Conference on Software Engineering, Singapore, April 1988, 396-406. C.A.R. Hoare, Communicating sequential processes. Commun. ACM 21, 8 (Aug. 1978), 666-677. C.A.R. Hoare, Communicating Sequential Processes. Prentice-Hall 1985. S. Katz, A superimposition control construct for distributed systems. Microelectronics and Computer Technology Corporation, Report STP-268-87, Aug. 1987.
7 of 7 [21] R. Kurki-Suonio, Towards programming with knowledge expressions. In Proc. 13th ACM Symposium on Principles of Programming Languages, St. Petersburg Beach, Florida, Jan. 1986, 140-149. [22] R. Kurki-Suonio, Specification of serializable databases by joint actions. Tampere University of Technology, Software Systems Laboratory, Report 2, 1988. [23] R. Kurki-Suonio and T. Kankaanpää, On the design of reactive systems. BIT 28, 3 (1988), 581-604. [24] Z. Manna and A. Pnueli, How to cook a temporal proof system for your pet language. In Proc. 10th ACM Symposium on Principles of Programming Languages, Austin, Texas, Jan. 1983, 141-154. [25] A. Pnueli, Applications of temporal logic to the specification and verification of reactive systems: a survey of current trends. In Current Trends in Concurrency (Ed. J.W. de Bakker, W.-P. de Roever and G. Rozenberg), LNCS 224, Springer-Verlag 1986, 510-584. [26] S. Ramesh and S.L. Mehndiratta, A methodology for developing distributed programs. IEEE Transactions on Software Engineering, SE-13, 8 (Aug. 1987), 967-976.