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.
In this section we will introduce the typed formal language PBC for the Propositional logic of Boolean Categories which may be regarded as loop-free propositional dynamic logic with data types. The main result is that the set of formulas valid in all Boolean categories coincides precisely with the set of formulas true in Mfn. We give two proofs. The first works only for atomic Boolean categories, but the construction is very straightforward. The second, general proof uses ultrafilters and follows that of [Kozen 1980] for dynamic algebras.
The language PDL of [Fischer and Ladner 1979] defines programs and formulas by mutual recursion as follows (where we have made notational changes to conform to previous sections of this paper):
Atomic programs are programs; ∅ is a program; if α, β are programs and P is a formula then αβ, α + β, α* and p (the guard corresponding to P) are programs.
Atomic formulas are formulas; true and false are formulas; if P, Q are formulas and α is a program then P∪Q, P′ and < α > P are formulas.
The Kripke model semantics which is standard for PDL is, essentially, Mfn so that α + β = α∪β and α* are available. In particular, if-then-else can be expressed as in 8.24. The identity program is expressible as 0*.
Formal semantics is a topic of major importance in the study of programming languages. Its applications include documenting language design, establishing standards for implementations, reasoning about programs, and generating compilers.
This book is about action semantics, a recently-developed framework for formal semantics. The primary aim of action semantics is to allow useful semantic descriptions of realistic programming languages.
Action semantics combines formality with many good pragmatic features. Regarding comprehensibility and accessibility, for instance, action semantic descriptions compete with informal language descriptions. Action semantic descriptions scale up smoothly from small example languages to full-blown practical languages. The addition of new constructs to a described language does not require reformulation of the already-given description. An action semantic description of one language can make widespread reuse of that of another, related language. All these pragmatic features are highly desirable. Action semantics is, however, the only semantic framework that enjoys them! (For syntax, context-free grammars have similar features, which perhaps accounts for their popularity in language reference manuals.)
Action semantics is compositional, like denotational semantics. The main difference between action semantics and denotational semantics concerns the universe of semantic entities: action semantics uses entities called actions, rather than the higher-order functions used with denotational semantics. Actions are inherently more operational than functions: when performed, actions process information gradually.
Primitive actions, and the various ways of combining actions, correspond to fundamental concepts of information processing. Action semantics provides a particular notation for expressing actions.
Denotational semantics has poor pragmatic features, due to its use of the λnotation.
Several so-called algebraic frameworks are essentially operational.
Genuinely axiomatic frameworks are prone to incompleteness and inconsistency.
Operational semantics is useful; action semantics makes it easier.
Action semantics is not the only available framework for giving formal semantic descriptions of programming languages. Other frameworks include denotational semantics, various kinds of algebraic semantics, axiomatic semantics, and operational semantics. What is wrong with them all? Why should one prefer action semantics—at least for describing full-scale, realistic programming languages?
Let us look briefly at most of the other frameworks in turn, pointing out their major defects in comparison to action semantics. To make the discussion accessible to those not familiar with the various frameworks, we indicate their main features, and cite primary references. However, a full appreciation of the comparisons requires in-depth familiarity with both action semantics and the other frameworks, as well as a more thorough discussion than can be provided here.
Almost all frameworks for formal semantics are based on context-free abstract syntax, although the notations that they use to specify abstract syntax are quite varied. The grammars that we use in action semantic descriptions perhaps have some significant advantages over other ways of specifying abstract syntax. But our main interest here is in the essential differences concerning the specification of semantics, once abstract syntax has been specified, so let us not pursue this point further.
Part II introduces the details of action notation systematically, explaining the underlying concepts and the intended interpretation of the symbols. It is intended to be read together with Part III, which illustrates the use of action notation in semantic descriptions.
Some of the Appendices provide further details of the foundations of action notation, and summarize the explanations given throughout Part II. Appendix B specifies some algebraic properties of the entire action notation; this also reduces action notation to a reasonably small kernel. Appendix C defines the abstract syntax of the kernel, and gives the formal definition of its meaning, using structural operational semantics. Appendix D summarizes the informal explanations of the full action notation, for convenience of reference. Appendix E gives the complete algebraic specification of the data notation included in action notation.
Navigation
If this is your first reading, proceed in parallel through Parts II and III: Chapter 4) Chapter 11, Chapter 5, Chapter 12, and so on. This way, you see an illustration of the use of each part of action notation immediately after its introduction
Alternatively, you could look at each chapter of Part III before the corresponding chapter of Part II. This way, the illustrations in Part III motivate the action notation introduced in Part II.
If you are revising, and would like an uninterrupted presentation of action notation, proceed straight through Part II.
Part II introduces Boolean categories and establishes their basic structure. A Boolean category is intended to be a minimal framework for the semantics of loop-free control structures including a high-level specification language based on propositional dynamic logic. Coproducts are given a fundamental role in this approach. The axioms on a Boolean category are designed to justify the intuition that a binary coproduct decomposes an object into two disjoint alternatives.
Basic statements include sequencing, choices, loops, and exits.
The semantic description of statements illustrates the use of the basic action notation introduced in Chapter 4.
No special semantic entities are required.
Statements, sometimes called commands, are basic constructs of many programming languages. Together with other constructs, they mainly serve to specify the order in which parts of a program are intended to be performed. We call the performance of a statement its execution.
Implementations usually follow the intended order of performance, although any order providing the same observable behaviour is allowed. Moreover, when the intended order is only partially specified, implementations are free to choose any order consistent with the specified order. Of course, when different orders of performance can lead to different observable behaviours, an implementation only provides one of the possible behaviours in each execution.
The so-called imperative programming languages contain a wide variety of statements. This is in contrast to functional programming languages, where the primary concern is with data rather than with order of performance. Most statements, however, have other kinds of phrases as components, such as expressions and declarations. The few statements that do not involve other kinds of construct don't provide much of a programming language: they allow for sequences, nondeterministic choices, and loops, but not much else. Nevertheless, the semantic description of these simple statements does provide some illustration of the use of the basic action notation introduced in Chapter 4.
Declarations of variables involve types, which indicate the values that may be assigned to the variables. Assignments are statements.
The semantics of variable declarations and assignment statements illustrates the use of the imperative action notation introduced in Chapter 8.
Semantic entities now include variables, types, and access values.
In programs, variables are entities that refer to stored data. The value of a variable is the data to which it currently refers; a variable may be assigned a succession of different values.
This concept of a program variable is quite different from that of a mathematical variable. In mathematics, variables are used to stand for particular unknown values—often the arguments of functions being defined. Although these variables can be ‘assigned’ values, e.g., by function application, their values do not subsequently vary! In the scope of a variable, all occurrences of that variable refer to the same value. In fact mathematical variables correspond exactly to the constant identifiers of programming languages, described in Chapter 14.
A declaration of a variable in a program determines a new variable: one whose value is, in general, independent of that of the values of all other variables. This is called allocating the variable. The declaration then binds an identifier to the variable. Usually the declaration specifies the type of the variable, indicating what sort of value may be assigned to it.
Action semantic descriptions are divided into modules.
Grammars are used to specify abstract syntax.
Semantic equations are used to specify semantic functions.
Action semantics provides a standard notation for specifying semantic entities (actions, data, yielders).
The standard notation can be extended and specialized by means of algebraic specifications.
A semantic description consists of three main parts, concerned with specifying abstract syntax, semantic functions, and semantic entities. We specify these parts as separate modules, which, in larger specifications, may themselves be divided into submodules, just as we normally divide technical reports and textbooks into sections and subsections.
Let us adopt the following discipline in our modular specifications: each module has to have a title, and it has to be self-contained, in the sense that all the notation used in a module must be specified there too. Of course, when some module M uses notation that is already specified in another module M′, there is no point in repeating all of M′ literally in M: it is sufficient to refer to Mi from M, using its title. Similarly, when the submodules Mi of a module M use some common notation, we may as well specify that notation just once, at the level of M, letting it be inherited by each Mi. A reference to a module M thus provides not only the notation specified directly in M and its submodules, but also that which is specified in modules referenced by M and in supermodules enclosing M.
This Appendix gives some examples of possible course assessment projects. They concern MODULA-3, ADA, and functional programming languages. Your lecturer or supervisor might either let you choose between them, or designate which one you are to attempt.
Your completion of such a project not only documents your active participation in the course, it also serves some important pedagogical purposes. For instance, it gives you the opportunity to revise the material already covered, it helps you appreciate the inherent modularity of action semantic descriptions, and it lets you check whether you have indeed acquired a working knowledge of action semantics.
You are advised to finish the description of straightforward constructs before proceeding to the more challenging ones. Try to reuse the description of AD as much as possible!
At Aarhus, the students are expected to work in groups of two or three, handing in as much as they can manage after 25 hours work—without sacrificing quality for quantity! They are given access to the LATEX formatting macros used for the semantic descriptions in this book, and to the LATEX source for Appendix A.
Actions are semantic entities, used to represent behaviour.
Action notation includes a basic action notation for specifying control flow.
Basic actions are not explicitly concerned with any particular kind of information.
Chapter 11 illustrates the use of basic action notation in the semantic description of statements.
Actions are semantic entities that represent potential information-processing behaviour, i.e., the semantics of programs and of their component phrases, such as statements. Each performance of an action corresponds to a possible behaviour. In this chapter, we restrict our attention to the actions provided by basic action notation. These basic actions are closely related to fundamental concepts of control flow. In subsequent chapters, we enhance basic actions to process various kinds of information, and introduce further actions.
Before we look at our formal notation for actions, let us consider the concepts of control flow that underlie the intended interpretation of the notation. An action performance consists of some atomic steps, made by one or more agents. For now we only consider single-agent performances, and we assume that in any particular performance, steps occur in a definite order, rather than concurrently. This ensures that the current information available when performing a step is well-defined, and that conflicting changes to the current information cannot arise. Chapter 10 explains multi-agent performances, where a dynamically-changing distributed system of agents proceeds to perform separate actions with ‘true’ concurrency, with asynchronous sending of messages between agents.
The meta-notation used in this book consists of positive Horn clauses, constraints, and modules, together with some convenient abbreviations.
The informal summary of the meta-notation given in this Appendix provides a concise explanation of each construct.
The description of the formal abbreviations used in the meta-notation reduces the metanotation to a simple kernel.
A context-free grammar specifies the abstract syntax of the meta-notation, and suggests its concrete syntax.
The logic used for reasoning about the meta-notation consists of the standard inference rules for Horn clause logic with equality, together with some Horn clause axioms.
The formal semantics of the meta-notation is, unfortunately, out of the scope of this book.
Informal Summary
Meta-notation is for specifying formal notation: what symbols are used, how they may be put together, and their intended interpretation.
Our meta-notation here supports a unified treatment of sorts and individuals: an individual is treated as a special case of a sort. Thus operations can be applied to sorts as well as individuals. A vacuous sort represents the lack of an individual, in particular the undefined result of a partial operation. Sorts may be related by inclusion; sort equality is just mutual inclusion. But a sort is not determined just by the set of individuals that it includes: it has an intension, stemming from the way it is expressed.
Reflective action notation is for specifying abstractions and their enaction.
Abstractions are data that incorporate actions. The incorporated action can be supplied with transients and bindings.
Chapter 16 illustrates the use of reflective action notation in the semantic description of procedure declarations.
An abstraction is a datum that merely incorporates a particular action. It corresponds to the ‘code’ for the action, which could be implemented as a sequence of machine instructions, or as a pointer to such a sequence. We use abstractions to represent the semantics of programming constructs such as procedures.
We may form an abstraction directly from an action. The abstraction, just like any other datum, can then be given as a transient, bound to a token, etc. Ultimately, the abstraction should be enacted, so as to perform the incorporated action.
Forming an abstraction may be regarded as a kind of reification, and enacting it as reflection. Reification and reflection are general concepts concerned with passing from components of an implicit, underlying computational state to values that can be manipulated explicitly, and vice versa [FW84]. Thus the evaluation of yielders for referring to the given transients, current bindings, etc., can also be regarded as reification, with the performance of primitive actions such as give Y and produce Y corresponding to reflection.
The action incorporated in an abstraction is usually performed in a context different to that where the abstraction itself occurs.
The abstract syntax of a kernel action notation is the basis for its formal semantic description. The full action notation can be reduced to the kernel by using some of the algebraic properties specified in Appendix B.
The specification of semantic entities shows what kind of states are needed to support action performance.
The structural operational semantics of action notation gives the formal definition of action performance.
The definition of observational and testing equivalence on actions relates the operational semantics of action notation to the algebraic properties specified in Appendix B.
The standard action notation used in action semantics allows direct expression of control, dataflow, scopes of bindings, changes to storage, and communication between distributed agents. It is therefore to be expected that this Appendix, which contains its complete operational semantic description, makes a formidable document. The modular structure is shown on the next page. The modules are followed by the definition of observational and testing equivalence.
Whereas standard action notation is semantically rich, the kernel of action notation is syntactically of only moderate size. This is revealed by the grammar specifying the abstract syntax of the kernel. There are eight fundamental binary combinators, and four hybrid combinators. Disregarding the unfolding notation used to abbreviate infinite action terms, there are only two unary combinators in the kernel.
The metatheory so far has emphasized formulas. The more general first-order theory of Boolean categories must consider statements about both morphisms and formulas together. In this section we consider the equational theory of if-then-else in the “3-valued case” in which the corresponding choice operator is deterministic but not necessarily idempotent as was previously true in Proposition 5.13. Since “if P then α else α = α” is essentially the law of the excluded middle for P, we are therefore allowing such a law to fail, that is, “tests need not halt”. This is realistic in any context in which a test can query the result of an arbitrary computable function.
After characterizing 3-valued if-then-else in Boolean categories we pave the way for the main result, Theorem 14.9 below, as succinctly as possible. The proofs, which are of a universal-algebraic nature, are in [Manes, 1992]; the gap to be bridged in this section is to elevate the results of that paper to the setting of preadditive Boolean categories with projection system. At this time, however, the main result applies only to the semilattice-assertional case.
DEFINITION Let B be a Boolean algebra. The elements of B are “2-valued propositions”. A 3-valued proposition on B is a pair P = (PF, PT) with PF, PT ∈ B, PFPT = 0.
Literals are lexical symbols including numerals, characters, and strings.
The semantic description of literals is facilitated by standard operations on strings.
The semantic entities required are specified ad hoc, exploiting the general data notation introduced in Chapter 5.
Literals are the simplest constructs of programming languages, with regard to both syntax and semantics. Typical examples are numeric literals, i.e., numerals, and string literals.
The description of literals is not very challenging. But we have to take care to avoid overspecification that might place unintended and impractical burdens on implementations.
For instance, it might be imagined that we could adapt the description of binary numerals in Chapter 2 immediately to decimal integer numerals, such as those in ADA. However, in the absence of syntactic limits on the length of numerals, the corresponding semantic entities would be arbitrarily-large numbers. Practical programming languages generally put implementation-dependent bounds on the magnitude of numbers. In ADA, the constants MIN_INT and MAX_INT bound the required integers, although implementations are allowed to provide further integer ‘types’ with implementation-dependent bounds.
The semantics of an integer numeral is the expected mathematical value only when that lies within bounds. Otherwise it should be some entity that represents a numeric error. The situation with literals for so-called ‘real’ numbers is similar, although it is necessary to take account of implementation-dependent lower bounds on the accuracy of ‘real’ numbers, as well as bounds on their magnitude.
Algebraic specifications are used here for specifying standard notation for data, and for extending and specializing our standard notation for actions.
Our unorthodox framework for algebraic specification copes well with partial operations, generic data, polymorphism, and nondeterminism.
Algebraic specifications introduce symbols, assert axioms, and constrain models, as illustrated here by a natural example.
The grammars and semantic equations used in action semantic descriptions can easily be regarded as algebraic specifications.
The structural operational semantics of action notation is written as an algebraic specification of a transition function.
This chapter is mainly concerned with the details of how we specify notation for data. It also introduces the foundations of action semantics. Some readers may prefer to defer the detailed study of the example given here, because when reading action semantic descriptions one may take it for granted that data can indeed be specified algebraically, and that foundations do exist. Moreover, action notation does not depend much on the unorthodox features of our algebraic specification framework. On the other hand, readers who are used to conventional algebraic specifications are advised to look closely at this chapter before proceeding, as they may otherwise find it difficult to reconcile our examples with their experience.
First, some standard terminology. An algebra consists essentially of a universe, or carrier, of values, together with some distinguished operations on values. Operations of no arguments are called constants.
Part III initiates the study of a few issues concerned with the first-order theory of Boolean categories. “Formulas” are summands expressible in the language PBC of Boolean categories introduced in Section 11. The fundamental completeness theorem 11.15 asserts that a formula is universally valid if and only if it is true in the classical example of Mfn. A similar result holds for ranged Boolean categories. Using a completion by ideals developed in Section 13, the forward predicate transformers are seen to be categorically dual to the inverse ones, at least in the preadditive case. Section 14 provides the equational theory for 3-valued if-then-else in a Boolean category.