We use cookies to distinguish you from other users and to provide you with a better experience on our websites. Close this message to accept cookies or find out how to manage your cookie settings.
To save content items to your account,
please confirm that you agree to abide by our usage policies.
If this is the first time you use this feature, you will be asked to authorise Cambridge Core to connect with your account.
Find out more about saving content to .
To save content items to your Kindle, first ensure no-reply@cambridge.org
is added to your Approved Personal Document E-mail List under your Personal Document Settings
on the Manage Your Content and Devices page of your Amazon account. Then enter the ‘name’ part
of your Kindle email address below.
Find out more about saving to your Kindle.
Note you can select to save to either the @free.kindle.com or @kindle.com variations.
‘@free.kindle.com’ emails are free but can only be saved to your device when it is connected to wi-fi.
‘@kindle.com’ emails can be delivered even when you are not connected to wi-fi, but note that service fees apply.
The static projection tells us which part of a function's argument will be present during partial evaluation. In any particular call of the function, this part of the argument is used in the production of a residual function. However, this still leaves the question: which part of the argument should the residual function be given at run-time? Obviously we could pass the whole argument if we wanted to, but we can do a lot better. After all, we assume that the partial evaluator will have taken the static part of the argument into account in producing the residual function. It ought to be unnecessary to supply the residual function with the same information all over again.
We need a way to select the run-time information. The original argument to a function ƒ must be factorised, or decomposed, into static and dynamic factors, and this factorisation should be as complete as possible. That is, the amount of static information which is also regarded as dynamic should be minimised. Then, when we pass the dynamic argument to the residual function, we will be passing as little information at run-time as possible. There are, of course, many possible factorisation methods. Some produce an exact decomposition while others do not in that they contain extra junk.
We will look at two methods in this chapter. While the first does not produce an exact factorisation of the original argument, it is based on very familiar constructions and is interesting in its own right. The second method, which is exact, arises as a generalisation of the first, and provides a practical application of some fairly deep mathematics.
The equations for mix assume that it is operating on a two argument function where the first argument is static and the second dynamic. This is the canonical case. In practice we cannot hope that all functions will turn out this way. For example, a function may have many arguments, the first and third being static, say. Alternatively, a single argument may have both static and dynamic parts. We need a framework for reducing the general case to the canonical case.
We can simplify the general case by requiring that all functions have exactly one argument. In first-order languages this is no real restriction. Functions must always be applied to all their arguments, so we can just express them as a single tuple. The next stage is to factorise this single (composite) argument into two parts, the static and the dynamic. We use the results of binding-time analysis to control the factorisation.
Note that, even though functions will only have one argument, we will still loosely describe them as having many. For example, we will talk of a function f (x, y) = … as having two arguments when this is appropriate.
Motivation
For the present we will focus our attention on the static part of the argument. To select the static part, we use a function from the argument domain to some domain of static values. If we make the static domain a sub-domain of the original we can simply “blank out” the dynamic part of the argument and leave the static part unchanged.
This thesis is submitted in partial fulfillment of the requirements for a Doctor of Philosophy Degree at Glasgow University. It comprises a study of partial evaluation, with the thesis that domain projections provide an important theoretical and practical tool for its development.
Our aim, therefore, is not so much to describe a stronger or more robust partial evaluator than has been achieved hitherto, but to improve our understanding of the partial evaluation process. Because of this much of the thesis is theoretical. However, to demonstrate that the ideas are also practical, they have been implemented. As a result, the chapters tend to alternate between theory and practice.
In Chapter 1 we explore the principles of partial evaluation and in Chapter 2 we study the algorithms and techniques used. In Chapters 3 and 4 we address the issue of binding-time analysis. Chapter 3 contains theory, including the relationship between congruence in binding-time analysis and safety in strictness analysis, and Chapter 4 the practice-the equations used in an implementation and a proof of their correctness. In Chapter 5, we discuss the nature of residual functions and their run-time arguments, and develop a theoretical framework based on dependent sums of domains. The practical implications of this are seen in Chapter 6 where we bring the material from the previous chapters together in a working projection-based partial evaluator. In Chapter 7 we turn our attention to polymorphism to address some of the issues it raises, and Chapter 8 concludes the thesis. The appendices which follow contain annotated listings of the programs used to construct the final polymorphic partial evaluator.
Our first view of a concurrent process is that of a machine where every detail of its behaviour is explicit. We could take as our machine model automata in the sense of classical automata theory [RS59], also known as transition systems [Kel76]. Automata are fine except that they cannot represent situations where parts of a machine work independently or concurrently. Since we are after such a representation, we use Petri nets [Pet62, Rei85] instead. This choice is motivated by the following advantages of nets:
Concepts. Petri nets are based on a simple extension of the concepts of state and transition known from automata. The extension is that in nets both states and transitions are distributed over several places. This allows an explicit distinction between concurrency and sequentiality.
Graphics. Petri nets have a graphical representation that visualises the different basic concepts about processes like sequentiality, choice, concurrency and synchronisation.
Size. Since Petri nets allow cycles, a large class of processes can be represented by finite nets. Also, as a consequence of (1), parallel composition will be additive in size rather than multiplicative.
An attractive alternative to Petri nets are event structures introduced in [NPW81] and further developed by Winskel [Win80, Win87]. Event structures are more abstract than nets because they do not record states, only events, i.e. the occurences of transitions. But in order to forget about states, event structures must not contain cycles. This yields infinite event structures even in cases where finite (but cyclic) nets suffice.
Many computing systems consist of a possibly large number of components that not only work independently or concurrently, but also interact or communicate with each other from time to time. Examples of such systems are operating systems, distributed systems and communication protocols, as well as systolic algorithms, computer architectures and integrated circuits.
Conceptually, it is convenient to treat these systems and their components uniformly as concurrent processes. A process is here an object that is designed for a possibly continuous interaction with its user, which can be another process. An interaction can be an input or output of a value, but we just think of it abstractly as a communication. In between two subsequent communications the process usually engages in some internal actions. These proceed autonomously at a certain speed and are not visible to the user. However, as a result of such internal actions the process behaviour may appear nondeterministic to the user. Concurrency arises because there can be more than one user and inside the process more than one active subprocess. The behaviour of a process is unsatisfactory for its user(s) if it does not communicate as desired. The reason can be that the process stops too early or that it engages in an infinite loop of internal actions. The first problem causes a deadlock with the user(s); the second one is known as divergence. Thus most processes are designed to communicate arbitrarily long without any danger of deadlock or divergence.
A crucial test for any theory of concurrent processes is case studies. These will clarify the application areas where this theory is particularly helpful but also reveal its shortcomings. Such shortcomings can be challenges for future research.
Considering all existing case studies based on Petri nets, algebraic process terms and logical formulas, it is obvious that these description methods are immensely helpful in specifying, constructing and verifying concurrent processes. We think in particular of protocol verification, e.g. [Vaa86, Bae90], the verification of VLSI algorithms, e.g. [Hen86], the design of computer architectures, e.g. [Klu87, DD89a, DD89b], and even of concurrent programming languages such as OCCAM [INM84, RH88] or POOL [Ame85, ABKR86, AR89, Vaa90]. However, these examples use one specific description method in each case.
Our overall aim is the smooth integration of description methods that cover different levels of abstraction in a top-down design of concurrent processes. This aim is similar to what Misra and Chandy have presented in their rich and beautiful book on UNITY [CM88]. However, we believe that their approach requires complementary work at the level of implementation, i.e. where UNITY programs are mapped onto architectures.
Our presentation of three different views of concurrent processes attempts to contribute to this overall aim. To obtain a coherent theory, we concentrated on a setting where simple classes of nets, terms and formulas are used. We demonstrated the applicability of this setting in a series of small but non-trivial process constructions.
The stepwise development of complex systems through various levels of abstraction is good practice in software and hardware design. However, the semantic link between these different levels is often missing. This book is intended as a detailed case study how such links can be established. It presents a theory of concurrent processes where three different semantic description methods are brought together in one uniform framework. Nets, terms and formulas are seen as expressing complementary views of processes, each one describing processes at a different level of abstraction.
Petri nets are used to describe processes as concurrent and interacting machines which engage in internal actions and communications with their environment or user.
Process terms are used as an abstract concurrent programming language. Due to their algebraic structure process terms emphasise compositionality, i.e. how complex terms are composed from simpler ones.
Logical formulas of a first-order predicate logic, called trace logic, are used as a specification language for processes. Logical formulas specify safety and liveness aspects of the communication behaviour of processes as required by their users.
At the heart of this theory are two sets of transformation rules for the top-down design of concurrent processes. The first set can be used to transform logical formulas stepwise into process terms, and the second set can be used to transform process terms into Petri nets. These rules are based on novel techniques for the operational and denotational semantics of concurrent processes.