-----Заголовок-----
Источник: http://picxxx.info
Ссылка на PDF: http://picxxx.info/pml.php?action=GETCONTENT&md5=794bd80710aef0909d4684ff02a795a8
-----Конец заголовка-----
A Structural Approach to Operational
Semantics
Gordon D. Plotkin
Laboratory for Foundations of Computer Science, School of Informatics,
University of Edinburgh, King’s Buildings, Edinburgh EH9 3JZ, Scotland
Contents
1
Transition Systems and Interpreting Automata
3
1.1
Introduction
3
1.2
Transition Systems
3
1.3
Examples of Transition Systems
5
1.4
Interpreting Automata
12
1.5
Exercises
18
2
Bibliography
23
3
Simple Expressions and Commands
24
3.1
Simple Expressions
24
3.2
Simple Commands
31
3.3
L-commands
34
3.4
Structural Induction
37
3.5
Dynamic Errors
41
3.6
Simple Type-Checking
42
3.7
Static Errors
45
Email address: gdp@inf.ed.ac.uk (Gordon D. Plotkin).
Preprint submitted to Journal of Logic and Algebraic Programming
30 January 2004
3.8
Exercises
46
3.9
Bibliographical Remarks
51
4
Bibliography
52
5
Definitions and Declarations
54
5.1
Introduction
54
5.2
Simple Definitions in Applicative Languages
54
5.3
Compound Definitions
58
5.4
Type-Checking and Definitions
65
5.5
Exercises
79
5.6
Remarks
85
6
Bibliography
86
7
Functions, Procedures and Classes
88
7.1
Functions in Applicative Languages
89
7.2
Procedures and Functions
102
7.3
Other Parameter Mechanisms
107
7.4
Higher Types
114
7.5
Modules and Classes
117
7.6
Exercises
124
A
A Guide to the Notation
130
B
Notes on Sets
131
2
1
Transition Systems and Interpreting Automata
1.1 Introduction
It is the purpose of these notes to develop a simple and direct method for specifying the semantics of programming languages. Very little is required in the way of mathematical background;
all that will be involved is “symbol-pushing” of one kind or another of the sort which will already be familiar to readers with experience of either the non-numerical aspects of programming
languages or else formal deductive systems of the kind employed in mathematical logic.
Apart from a simple kind of mathematics the method is intended to produce concise comprehensible semantic definitions. Indeed the method is even intended as a direct formalisation
of (many aspects of) the usual informal natural language descriptions. I should really confess
here that while I have some experience what has been expressed above is rather a pious hope
than a statement of fact. I would therefore be most grateful to readers for their comments and
particularly their criticisms.
I will follow the approach to programming languages taken by such authors as Gordon [Gor] and
Tennent [Ten] considering the main syntactic classes – expressions, commands and declarations
– and the various features found in each. The linguistic approach is that developed by the ScottStrachey school (together with Landin and McCarthy and others) but within an operational
rather than a denotational framework. These notes should be considered as an attempt at
showing the feasibility of such an approach. Apart from various inadequacies of the treatment
as presented many topics of importance are omitted. These include data structures and data
types; various forms of control structure from jumps to exceptions and coroutines; concurrency
including semaphores, monitors and communicating process.
Many thanks are due to the Department of Computer Science at Aarhus University at whose
invitation I was enabled to spend a very pleasant six months developing this material. These
notes partially cover a series of lectures given at the department. I would like also to thank the
staff and students whose advice and criticism had a strong influence and also Jette Milwertz
whose typing skills made the work look better than it should.
1.2 Transition Systems
The announced “symbol-pushing” nature of our method suggests what is the truth; it is an
operational method of specifying semantics based on syntactic transformations of programs
and simple operations on discrete data. The idea is that in general one should be interested in
computer systems whether hardware or software and for semantics one thinks of systems whose
configurations are a mixture of syntactical objects – the programs and data – such as stores or
3
environments. Thus in these notes we have
SYSTEM = PROGRAM + DATA
One wonders if this study could be generalised to other kinds of systems, especially hardware
ones.
Clearly systems have some behaviour and it is that which we wish to describe. In an operational semantics one focuses on the operations the system can perform – whether internally
or interactively with some supersystem or the outside world. For in our discrete (digital) computer systems behaviour consists of elementary steps which are occurrences of operations. Such
elementary steps are called here, (and also in many other situations in Computer Science) transitions (= moves). Thus a transition steps from one configuration to another and as a first idea
we take it to be a binary relation between configurations.
Definition 1 A Transition System (ts) is (just!) a structure Γ, −→ where Γ is a set (of
elements, γ, called configurations) and −→ ⊆ Γ × Γ is a binary relation (called the transition
relation). Read γ −→ γ as saying that there is a transition from the configuration γ to the
configuration γ . (Other notations sometimes seen are , ⇒ and ✄).
...... ....
.........
❅❅
❅
❅ ...
❅
❅
❅
❅
❅
❅ ❅
❅
...... ❅
❅
❅
......❅
❅
❅
❅
❅
.
...
❅
❅ ❅
❅❅
.
❅
❅
.
❅
.....
..
❅
❅ ❅
❅
.
......
❅
❅
❅ ❅ ❅
..
❅
❅
❅ ❅
❅
❅
❅
❅.
❅ ❅
❅ ...
❅ ❅
❅
❅ ❅
❅❅
❅
❅
❅
❅ ❅
.
.....
❅
❅..
❅ ❅
❅ ❅
.
❅...
.❅
..... ❅ ❅ ..❅
..
.
.
❅.
❅
❅ ❅
✲
. ...... ❅
❅❅
....
❅
❅ ❅
❅❅
❅...... .......
❅
.❅
..... ❅
❅
❅
❅ ❅
. ❅
❅
❅
.
❅
....❅
❅
❅
.. ❅ ❅ ❅
❅
❅...
❅ ❅ ❅
.
.
.
.
.
❅ ❅
❅
...❅.
❅
❅
❅
❅
❅
.. . ❅ ❅ ....
.. ....
..
.❅
.... ❅
❅
❅ ❅ ❅
. ❅
❅ ❅ ❅
❅
.❅
..... ❅ ❅ ❅ ❅ ❅
.
❅
❅ ❅ ❅
.
....❅
...❅
..
...
....❅
..❅
....❅
...❅
γ
γ
A Transition
Of course this idea is hardly new and examples can be found in any book on automata or formal
languages. Its application to the definition of programming languages can be found in the work
of Landin and the Vienna Group [Lan,Oll,Weg].
Structures of the form, Γ, −→ are rather simple and later we will consider several more
elaborate variants, tailored to individual circumstances. For example it is often helpful to have
an idea of terminal (= final = halting) configurations.
4
Definition 2 A Terminal Transition System (tts) is a structure Γ, −→, T where Γ, −→ is
a ts, and T ⊆ Γ (the set of final configurations) satisfies ∀γ ∈ T ∀γ ∈ Γ . γ −→ γ .
A point to watch is to make a distinction between internal and external behaviour. Internally
a system’s behaviour is nothing but the sum of its transitions. (We ignore here the fact that
often these transitions make sense only at a certain level; what counts as one transition for one
purpose may in fact consist of many steps when viewed in more detail. Part of the spirit of our
method is to choose steps of the appropriate “size”.) However externally many of the transitions
produce no detectable effect. It is a matter of experience to choose the right definition of external
behaviour. Often two or more definitions of behaviour (or of having the same behaviour) are
possible for a given transition system. Indeed on occasion one must turn the problem around and
look for a transition system which makes it possible to obtain an expected notion of behaviour.
1.3 Examples of Transition Systems
We recall a few familiar and not so familiar examples from computability and formal languages.
1.3.1
Finite Automata
A finite automaton is a quintuplet M = Q, Σ, δ, q0 , F where
•
•
•
•
•
Q is a finite set (of states)
Σ is a finite set (the input alphabet)
δ : Q × Σ −→ P(Q) (is the state transition relation)
q0 ∈ Q (is the initial state)
F ⊆ Q (is the set of final states)
To obtain a transition system we set
Γ = Q × Σ∗
So any configuration, γ = q, w has a state component, q, and a control component, w, for
data.
For the transitions we put whenever q ∈ δ(q, a):
q, aw
q ,w
(More formally,
= { q, aw , q , w
| q, q ∈ Q, a ∈ Σ, w ∈ Σ∗ , q ∈ δ(q, a)}).
The behaviour of a finite automaton is just the set L(M ) of strings it accepts:
L(M ) = {w ∈ Σ∗ | ∃q ∈ F q0 , w
∗
q, ε }
5
Of course we could also define the terminal configurations by:
T = { q, ε | q ∈ F }
and then
L(M ) = {w ∈ Σ∗ | ∃γ ∈ T q0 , w
∗
γ}
In fact we can even get a little more abstract. Let Γ, −→, T be a tts. An input function for it
is any mapping in: I −→ Γ and the language it accepts is then L(Γ) ⊆ I where:
L(Γ) = {i ∈ I | ∃γ ∈ T . in(i)−→∗ γ}
(For finite automata as above we take I = Σ∗ , and in(w) = q0 , w ). Thus we can easily
formalise at least one general notion of behaviour.
Example 3 The machine:
start ✲
1
0
0,1
✗✔
✗✔
✗✔
❄
❄
❄
✬✩
✬✩ ✬✩
✎0 ☞
✛✘
❄
0✲
p
q
r
✚✙
✍
✌
✻
✫✪
✫✪ ✫✪
1
A transition sequence:
p, 01001
q, 1001
p, 001
q, 01
r, 1
r, ε
1.3.2
Three Counter Machines
We have three counters, C, namely I, J and K. There are instructions, O, of the following four
types:
•
•
•
•
Increment: inc C : m
Decrement: dec C : m
Zero Test: zero C : m/n
Stop: stop
Then programs are just sequences P = O1 , . . . , Ol of instructions. Now, fixing P , the set of
configurations is:
Γ = { m, i, j, k | 1 ≤ m ≤ l; i, j, k ∈ N}
6
Then the transition relation is defined in terms of the various possibilities by:
• Case II: Om = inc I : m
m, i, j, k
m , i + 1, j, k
• Case ID: Om = dec I : m
m, i + 1, j, k
m , i, j, k
• Case IZ: Om = zero I : m /m
m, 0, j, k
m , 0, j, k
m, i + 1, j, k
m , i + 1, j, k
and similarly for J and K.
Note 1 There is no case for the stop instruction.
Note 2 In case m or m are 0 or > k the above definitions do not (of course!) apply.
Note 3 The transition relation is deterministic, that is:
∀γ, γ , γ · γ −→ γ ∧ γ −→ γ ⇒ γ = γ
or, diagrammatically:
γ
✡ ❏
✡
❏
✡
❏
✡
❏
✡
❏❏
✢
✡
❫
γ ======= γ
(Exercise – prove this).
Now the set of terminal configurations is defined by:
T = { m, 0, j, 0 | Om = stop}
and the behaviour is a partial function f : N −→ N where:
P
f (i) = j
def
=
1, i, 0, 0 −→∗ m, 0, j, 0 ∈ T
This can be put a little more abstractly, if we take for any tts Γ, −→, T an input function,
in : I −→ Γ as before and also an output function, out : T −→ O and define a partial function
fΓ : I −→ O by
P
fΓ (i) = o
≡
∃γ in(i) −→∗ γ ∈ T ∧ o = out(γ)
7
Of course for this to make sense the tts must be deterministic (why?). In the case of a threecounter machine we have
I=O=N
in(i) = 1, i, 0, 0
out( m, i, j, k ) = j
Example 4 A program for the successor function, n → n + 1
inc J
❄
✟❍
❍
✟
❍❍
✟✟
❍❍ yes ✲
✟
✟
zero I
❍❍
✟
✟✟
❍
✟
❍❍ ✟
❍✟
stop
no
❄
dec I
✻
❄
inc J
1.3.3
Context-Free Grammars
A context-free grammar is a quadruple, G = N, Σ, P, S where
•
•
•
•
N is a finite set (of non-terminals)
Σ is a finite set (the input alphabet)
P ⊆ N × (N ∪ Σ)∗ (is the set of productions)
S ∈ N (is the start symbol)
Then the configurations are given by:
Γ = (N ∪ Σ)∗
and the transition relation ⇒ is given by:
wXv ⇒ wxv
(when X → x is in P )
8
Now the behaviour is just
L(G) = {w | S ⇒∗ w}
Amusingly, this already does not fit into our abstract idea for behaviours as sets (the one which
worked for finite automata). The problem is that was intended for acceptance where here we
have to do with generation (by leftmost derivations).
Exercise: Write down an abstract model of generation.
Example 5 The grammar is:
S→
S → (S)
S → SS
and a transition sequence could be
S ⇒ SS ⇒ (S)S ⇒ ()S ⇒ ()(S)
⇒ ()(SS) ⇒2 ()(()S) ⇒2 ()(()())
1.3.4
Labelled Transition Systems
Transition systems in general do not give the opportunity of saying very much about any
individual transition. By adding the possibility of such information we arrive at a definition.
Definition 6 A Labelled Transition System (lts) is a structure Γ, A, −→ where Γ is a set (of
configurations) and A is a set (of actions (= labels = operations)) and
−→ ⊆ Γ × A × Γ
is the transition relation.
a
We write a transition as: γ −→ γ where γ, γ are configurations and a is an action. The idea
is that an action can give information about what went on in the configuration during the
transition (internal actions) or about the interaction between the system and its environment
(external actions) (or both). The labels are particularly useful for specifying distributed systems
where the actions may relate to the communications between sub-systems. The idea seems to
originate with Keller [Kel].
The idea of Labelled Terminal Transition Systems Γ, A, −→, T should be clear to the reader
who will also expect the following generalisation of reflexive (resp. transitive) closure. For any
9
lts let γ and γ be configurations and take x = a1 . . . ak in A+ (resp. A∗ ) then:
x + (resp.
γ −→
∗)
γ
def
=
a
a
1
k
∃γ1 , . . . , γk . γ −→
γ1 . . . −→
γk = γ
where k > 0 (resp. k ≥ 0).
Example 7 (Finite Automata (continued)) This time define a tts by taking
•
•
•
•
Γ=Q
A=Σ
a
q −→ q ≡ q ∈ δ(q, a)
T =F
w ∗
Then we have L(M ) = {w ∈ A∗ | ∃q ∈ T. q0 −→ q}. The example transition sequence given
above now becomes simply:
0
1
0
0
1
p −→ q −→ p −→ q −→ r −→ r ∈ F
Example 8 (Petri Nets) One idea of a Petri Net is just a quadruple N = B, E, F, m where
•
•
•
•
B is a finite set (of conditions)
E is a finite set (of events)
F ⊆ (B × E) ∪ (E × B) (is the flow relation)
m ⊆ B (is the initial case)
A configuration, m, is contact-free if
¬∃e ∈ E. (F −1 (e) ⊆ m ∧ F (e) ∩ m = ∅)
a
★✥
b
★✥
①
◗
◗◗
s
✧✦
◗◗
◗
✑✑
✑
★✥
✸
✑✑
① ✑
e
①
✑✑
✑
✧✦
✸
✑✑
✑
◗
◗◗
s
★✥
◗
◗◗
✧✦
✧✦
a
b
A contact situation for m = a, a , b
The point of this definition is that the occurrence of an event, e, is nothing more than the
ceasing-to-hold of its preconditions ( = F −1 (e)) and the starting-to-hold of its postconditions
( = F (e)) in any given case. Here a case is a set of conditions (those that hold in the case). A
10
contact-situation is one where this idea does not make sense. Often one excludes this possibility
axiomatically (and imposes also other intuitively acceptable axioms). We will just (somewhat
arbitrarily) regard them as “runtime errors” and take
Γ = {m ⊆ B | m is contact-free}
If two different events share a precondition in a case, then according to the above intentions
they cannot both occur at once. Accordingly we define a conflict relation between events by:
≡
ee
(F −1 (e) ∩ F −1 (e ) = ∅ ∧ e = e )
An event can occur from a given case if all its preconditions hold in the case. What is (much)
more, Petri Nets model concurrency in that several events (not in conflict) can occur together
in a given case. So we put
A = {X ⊆ E | ¬∃e, e ∈ X. e e }
and define
X
m −→ m
≡
F −1 (X) ⊆ m ∧ m = [m\F −1 (X)] ∪ F (X)
Here is a pictorial example of such a transition
★✥
★✥
★✥
★✥
★✥
★✥
★✥
★✥
①
①
✧✦
✧✦
✧✦
✧✦
✧✦
✧✦
✧✦
✧✦
✻
✻
✻
✻
✻
✻
✻
✻
{1,4}
✲
1
2
3
4
1
✏✏◗
❦◗
✸
✻✏✏
✑✑
✶✏
✐ ✻
✏
◗✑
★✥
★✥
★✥
①
①
2
3
4
✏✏◗
❦◗
✸
✻✏✏
✑✑
✶✏
✐ ✻
✏
◗✑
★✥
★✥
★✥
①
①
✧✦ ✧✦ ✧✦
✧✦ ✧✦ ✧✦
A Transition
We give no definition of behaviour as there does not seem to be any generally accepted one in
the literature. For further information on Petri Nets see [Bra,Pet].
Of course our transitions with their actions must also be thought of as kinds of events; even
more so when we are discussing the semantics of languages for concurrency. We believe there
are very strong links between our ideas and those in Net Theory, but, alas, do not have time
here to pursue them.
11
Example 9 (Readers and Writers) This is a (partial) specification of a Readers and Writers problem with two agents each of whom can read and write (and do some local processing)
but where the writes should not overlap.
★✥
★✥
✧✦
❅
❅
❅
✒
■
❅
❅
❅
✧✦
❅
❅
❅
✒
■
❅
❅
❅
FR1
FW1
❄
①
LP1
✧✦
SW1
❄
①
LP2
SW2
✒
❅★✥
FR2
✻
★✥
✧✦
✧✦
✧✦
❅
❅
✠
❘
❅
❅
❅ ✻
✻
❅
✻
❅
❅
❄
❅
❅
❘
❅
✠
✻ ❅
✻
❅
★✥
❅★✥
★✥
✻
★✥
SR1
FW2
✻
❄
❅
❅
❅
■
❅
❅
✧✦
SR2
✒
❅★✥
❅
■
❅
❅
①
✧✦
SWi is Start Writing
✧✦
i
FWi is Finish Writing i
SRi is Start Reading
i
FRi is Finish Reading i
LRi is Local Processing i
1.4
where 1 ≤ i ≤ 2
Interpreting Automata
To finish Chapter 1 we give an example of how to define the operational semantics of a language
by an interpreting automaton. The reader should obtain some feeling for what is possible along
these lines (see the references given above for more information), as well as a feeling that the
12
method is somehow a little too indirect thus paving the way for the approach taken in the next
chapter.
1.4.1
The Language L
We begin with the Abstract Syntax of a very simple programming language called L. What is
abstract about it will be discussed a little here and later at greater length. For us syntax is a
collection of syntactic sets of phrases; each set corresponds to a different type of phrase. Some
of these sets are very simple and can be taken as given:
• Basic Syntactic Sets
Truth-values This is the set T = {tt, ff} and is ranged over by (the metavariable) t (and
we also happily employ for this (and any other) metavariable sub- and super-scripts to
generate other metavariables: t , t0 , t1k ).
Numbers m, n are the metavariables over N = {0, 1, 2, . . .}.
Variables v ∈ Var = {a, b, c, . . . , z}
Note how we have progressed to a fairly spare style of specification in the above.
• Derived Syntactic Sets
Expressions e ∈ Exp given by
e ::= m | v | e + e | e − e | e ∗ e
Boolean Expressions b ∈ BExp given by
b ::= t | e = e | b or b | ∼b
Commands c ∈ Com given by
c ::= nil | v := e | c; c | if b then c else c | while b do c
This specification can be taken, roughly speaking, as a context-free grammar if the reader just
ignores the use of the infinite set N and the use of primes. It can also (despite appearances!) be
taken as unambiguous if the reader just regards the author as having lazily omitted brackets as
in:
b ::= t | e = e | b or b | ∼b
specifying parse trees so that rather than saying ambiguously that (for example):
while b do c; c
is a program what is being said is that both
13
❩
✡✡❏ ❩
✡✡❏
❏ ❩
❏
✡
✡
❏
❩
❏
✡
✡
❏ ❩❩
❏
and
✡
✡
❏
❏❏
❩
❏
❩
✡
✡
❏
✂
❅
✡
❇
while
b
do
;
c
❇
✡ ❏
✂ ❇❅
✡
❏
✂ ❇ ❅
✡
❏
✂
❅
❇
❏❏
❅
✡✡
✂✂
❅
❇
c
;
c
while
b
do
are trees.
c
So we are abstract in not worrying about some lexical matters and just using for example
integers rather than numerals and in not worrying about the exact specification of phrases.
What we are really trying to do is abstract away from the problems of parsing the token strings
that really came into the computer and considering instead the “deep structure” of programs.
Thus the syntactic categories we choose are supposed to be those with independent semantic
significance; the various program constructs – such as semicolon or while . . . do . . . – are the
constructive operations on phrases that possess semantic significance.
For example contrast the following concrete syntax for (some of) our expressions (taken from
[Ten]):
expression ::= term | expression
addop
term
term
::= factor | term
factor
::= variable | literal | ( expression )
addop
::= + | −
multop
::= ∗
variable
::= a | b | c | . . . | z
literal
::= 0 | 1 | . . . | 9
multop
factor
Now, however convenient it is for a parser to distinguish between expression , term and
factor it does not make much semantic sense!
Thus we will never give semantics directly to token strings but rather to their real structure.
However, we can always obtain the semantics of token strings via parsers which we regard as
essentially just maps:
Parser: Concrete Syntax −→ Abstract Syntax
Of course it is not really so well-defined what the abstract syntax for a given language is, and
we shall clearly make good use of the freedom of choice available.
14
Returning to our language L we observe the following “dependency diagram”:
C
❅
❅
❅
❅
❅
❅
❘
❅
✲ E
❄
B
1.4.2
The SMC-Machine
Now we define a suitable transition system whose configurations are those of the SMC-machine.
• Value Stacks is ranged over by S and is the set (T ∪ N ∪ Var ∪ BExp ∪ Com)∗
• Memories is ranged over by M and is Var −→ N
• Control Stacks is ranged over by C and is
(Com ∪ BExp ∪ Exp ∪ {+, −, ∗, =, or, ∼, :=, if , while})∗
The set of configurations is
Γ = Value Stacks × Memories × Control Stacks
and so a typical configuration is γ = S, M, C . The idea is that we interpret commands and
produce as our interpretation proceeds, stacks C, of control information (initially a command
but later bits of commands). Along the way we accumulate partial results (when evaluating
expressions), and bits of command text which will be needed later; this is all put (for some
reason) on the value stack, S. Finally we have a model of the store (= memory) as a function
M : Var −→ N which given a variable, v, says what its value M (v) is in the store.
Notation: In order to discuss updating variables, we introduce for a memory, M , natural
number, m, and variable v the memory M = M [m/v] where
M (v ) =
m
M (v )
(if v = v)
(otherwise)
So M [m/v] is the memory resulting from updating M by changing the value of v from M (v)
to m.
The transition relation, ⇒, is defined by cases according to what is on the top of the control
stack.
15
• Expressions
En
S, M, n C
⇒ n S, M, C
Ev
S, M, v C
⇒ M (v) S, M, C
+
E −I
∗
+
E −E
∗
+
+
S, M, e − e C
∗
⇒ S, M, e e − C
∗
+
m m S, M, − C
⇒ n S, M, C
∗
+
(where n = m − m )
∗
Note 1 The symbols +, −, ∗, are being used both as symbols of L and to stand for the
functions addition, subtraction and multiplication.
• Boolean Expressions
Bt
S, M, t C
⇒ t S, M, C
B=I
S, M, e = e C
⇒ S, M, e e = C
B=E
m m S, M, = C
⇒ t S, M, C
(where t = (m = m ))
B or I
S, M, b or b C
⇒ S, M, b b or C
B or E
t t S, M, or C
⇒ t S, M, C
(where t = (t ∨ t ))
B∼I
S, M, ∼ b C
⇒ S, M, b ∼ C
B∼E
t S, M, ∼ C
⇒ t S, M, C
(where t = ∼ t)
16
• Commands
C nil
S, M, nil C
⇒ S, M, C
C := I
S, M, v := e C
⇒ v S, M, e := C
C := E
m v S, M, := C
⇒ S, M [m/v], C
C;
S, M, c; c C
⇒ S, M, c c C
C if I
S, M, if b then c else c C
⇒ c c S, M, b if C
C if E
t c c S, M, if C
⇒ S, M, c C
(where if t = tt then c = c else c = c )
C while I
S, M, while b do c C
⇒ b c S, M, b while C
C while E1
tt b c S, M, while C
⇒ S, M, c while b do c C
C while E2
ff b c S, M, while C
⇒ S, M, C
Now that we have at some length defined the transition relation, the terminal configurations
are defined by:
T = { ε, M, ε }
and an input function in : Commands × Memories −→ Γ is defined by:
in(C, M ) = ε, M, C
and out : T −→ Memories by:
out( ε, M, ε ) = M
The behaviour of the SMC-machine is then a partial function, Eval : Commands×Memories −→
P
Memories and clearly:
Eval(C, M ) = M
ε, M, C ⇒∗ ε, M , ε
≡
Example 10 (Factorial)
C
y := 1; while ∼(x = 0) do y := y ∗ x; x := x − 1
C
ε, 3, 5 , y := 1; C
⇒ ε, 3, 5 , y := 1 C
⇒ y, 3, 5 , 1 := C
by C;
by C := I
17
⇒ 1 y, 3, 5 , := C
⇒ ε, 3, 1 , C
⇒ ∼(x = 0) C , 3, 1 , ∼(x = 0) while
⇒ ∼(x = 0) C , 3, 1 , (x = 0) ∼ while
⇒ ∼(x = 0) C , 3, 1 , x 0 = ∼ while
⇒ 3 ∼(x = 0) C , 3, 1 , 0 = ∼ while
⇒ 0 3 ∼(x = 0) C , 3, 1 , = ∼ while
⇒ ff ∼(x = 0) C , 3, 1 , ∼ while
⇒ tt ∼(x = 0) C , 3, 1 , while
⇒ ε, 3, 1 , C C
⇒ ε, 3, 1 , y := y ∗ x x := x − 1 C
⇒∗ ε, 3, 3 , x := x − 1 C
⇒∗ ε, 2, 3 , C
⇒∗ ε, 1, 6 , C
⇒∗ ε, 0, 6 , C
⇒ ∼(x = 0) C , 0, 6 , ∼(x = 0) while
⇒∗ ff ∼(x = 0) C , 0, 6 , while
⇒ ε, 0, 6 , ε
by
by
by
by
by
by
by
by
by
by
by
Em
C := E
C while I
E∼I
E=I
Ev
Em
E=E
E∼E
C while E1
C;
by C while I
by C while E2
Many other machines have been proposed along these lines. It is, perhaps, fair to say that
none of them can be considered as directly formalising the intuitive operational semantics to
be found in most language definitions. Rather they are more or less clearly correct on the basis
of this intuitive understanding. Further, although this is of less importance, they all have a
tendency to pull the syntax to pieces or at any rate to wander around the syntax creating
various complex symbolic structures which do not seem particularly forced by the demands
of the language itself. Finally, they do not in general have any great claim to being syntaxdirected in the sense of defining the semantics of compound phrases in terms of the semantics of
their components, although the definition of the transition relation does fall into natural cases
following the various syntactical possibilities.
1.5
Exercises
Finite Automata
Let M = Q, Σ, δ, q0 , F be a finite automaton.
1. Redefine the behaviour of M so that it accepts infinite strings a1 a2 . . . an . . ., that is so
that L(M ) ⊆ Σω . [Hint: There are actually two answers, which can with difficulty be
proved equivalent.]
18
2. Suppose that δ were changed so that the labelled transition relation had instead the form:
a
q −→ q1 , q2
and F so that F ⊆ Q × Σ. What is the new type of δ? How can binary trees like
a
✁❆
✁ ❆
✁
❆
❆
✁
b
c
✁❆
✁ ❆
✁
❆
✁
❆
d
now be accepted by M ?
e
3. Suppose instead transitions occurred with probability so that we had
a
q −→ q
p
with 0 ≤ p ≤ 1 and for any q and a:
a
Σ{p | q −→ q for some q } ≤ 1
p
What is a good definition of behaviour now?
4. Finite automata can be turned into transducer by taking δ to be a finite set of transitions
of the form:
v
q −→ q
w
v
with v, w ∈ Σ∗ . Define the relation q −→
q and the appropriate notion of behaviour.
w
Show any finite-state transducer can be turned into an equivalent one, where we have in
any transition that 0 ≤ |v| ≤ 1.
Various Machines
5. Define k counter machines. Show that any function computable by a k counter machine
is computable by a 3-counter machine. [Hint: First program elementary functions on the
3-counter machine including pairing, pair : N2 −→ N, and selection functions, fst, snd :
N −→ N such that:
fst(pair(m, n)) = m
snd(pair(m, n)) = n
19
Then simulate by coding all the registers of the k counter machine by a big tuple held in
one of the registers of the 3-counter machine.]
Show that any partial-recursive function (= one computable by a Turing Machine) can
be computed by some 3-counter machine (and vice-versa).
6. Consider stack machines where the registers hold stacks and operations on a stack (=
element of Σ∗ ) are pusha , pop, ishda (for each a ∈ Σ) given by:
pusha (w) = aw
pop(aw) = w
ishda (w) =
true
false
(if w = aw for some w )
(otherwise)
Show stack machines compute the same functions as Turing Machines. How many stacks
are needed at most?
7. Define and investigate queue machines.
8. See how your favourite machines (Turing Machines, Push-Down Automata) fit into our
framework. For a general view of machines, consult the eminently readable: [Bir] or [Sco].
Look too at [Gre].
Grammars
9. For CF grammars our notion of behaviour is adapted to generation. Define a notion that
is good for acceptance. What about mixed generation/acceptance? Change the definitions
so that you get parse trees as behaviour. What is the nicest way you can find to handle
syntax-directed translation schemes?
10. Show that for LL(1) grammars you can obtain deterministic labelled (with Σ) transitions
of the form
a
w −→ w
with w strings of terminals and non-terminals. What can you say about LL(k), LR(k)?
11. Have another look at other kinds of grammar too, e.g., Context-Sensitive, Type 0 (=
arbitrary) grammars. Discover other ideas for Transition Systems in the literature. Examples include: Tag, Semi-Thue Systems, Markov Algorithms, λ-Calculus, Post Systems,
L-Systems, Conway’s Game of Life and other forms of Cell Automata, Kleene’s Nerve
Nets . . .
20
Petri Nets
12. Show that if we have
m
X
✁
✁
✁
❆
❆
✁
✁☛✁
❆ X
❆
❆❆❯
m
m
where F −1 (X) ∩ F −1 (X ) = ∅ (i.e., no conflict between X and X ) then for some m we
have:
m
✡❏
❏
❏ X
❏
❏
❫
❏
X ✡✡
✡
✡
✡
✢
m
Y
m
✡
❏
✡
❏
✡ X
X ❏
✡
❏
❄
✡
❏
✢
❫✡
❏
m
This is a so-called Church-Rosser Property.
where Y = X ∪ X
X
13. Show that if we have m −→ m where X = {e1 , . . . , ek } then for some m1 , . . . , mk we
have:
{e1 }
{e2 }
{ek }
m −→ m1 −→ · · · −→ mk = m
What happens if we remove the restrictions on finiteness?
14. Write some Petri Nets for a parallel situation you know well (e.g., for something you knew
at home or some computational situation).
15. How can nets accept languages (= subsets of Σ∗ )? Are they always regular?
16. Find, for the Readers and Writers net given above, all the cases you can reach by transition
sequences starting at the initial case. Draw (nicely!) the graph of cases and transitions
(this is a so-called case graph).
Interpreting Automata
17. Let G = N, Σ, P, S be a context-free grammar. It is strongly unambiguous if there are
no two leftmost derivations of the same word in Σ∗ , even possibly starting from different
non-terminals. Find suitable conditions on the productions of P which ensure that G =
21
N, Σ , P , S is strongly unambiguous where Σ = Σ ∪ {(, )} where the parentheses are
assumed not to be in N or Σ and where
T −→ (w) is in P if T −→ w is in P .
18. See what changes you should make in the definition of the interpreting automaton when
some of the following features are added:
e ::= if b then e else e | begin c result e
c ::= if b then c |
case e of e1 : c
..
.
ek : c
end |
for v := e, e do c |
repeat c until b
19. Can you handle constructions that drastically change the flow of control such as:
c ::= stop | m : c | goto m
(Here stop just stops everything!)
20. Can you handle elementary read/write instructions such as:
c ::= read(v) | write(e)
[Hint: Consider an analogy with finite automata – especially transducers.]
21. Can you add facilities to the automaton to handle run-time errors?
22. Can you produce measures of time/space complexity by adding extra components to the
automaton?
23. Can you treat diagnostic (debugging, tracing) facilities?
24. What about real-time? That is suppose we had the awful expression:
e ::= time
which delivers the correct time.
25. Treat the following PASCAL subset. The basic sets are T, N and x ∈ I × {i, r, b} – the
set of typical identifiers (which is infinite) and o ∈ O – the set { =, <>, <, <=, >, >=,
22
+, −, ∗, /, div, mod, and } of operations. The idea for typical identifiers is that i, r, b
are type symbols for integer, real and boolean respectively and so FRED, r is the real
identifier FRED.
The derived sets are expressions and commands where:
e ::= m | t | v | −e | not e | e o e
c ::= nil | v := e | c; c | if e then c else c | while e do c
The point of the question is that you must think about compile-time type-checking and
the memories used in the S, M, C machine should be finite (even although there are
potentially infinitely many identifiers).
26. Can you treat the binding mechanism
s ::= i | r | b
c ::= var v : s begin c end
so that you must now incorporate symbol tables?
2
Bibliography
[Bir]
[Bra]
[Gor]
[Gre]
[Kel]
[Lan]
[Oll]
[Pet]
[Sco]
[Ten]
[Weg]
Bird, R. (1976) Programs and Machines, Wiley and Sons.
Brauer, W., ed. (1980) Net Theory and Applications, LNCS 84, Springer.
Gordon, M.J. (1979) The Denotational Description of Programming Languages,
Springer.
Greibach, S.A. (1975) Theory of Program Structures: Schemes, Semantics, Verification,
LNCS 36, Springer.
Keller, R.M. (1976) Formal Verification of Parallel Programs, Communications of the
ACM 19(7):371–384.
Landin, P.J. (1966) A Lambda-calculus Approach, Advances in Programming and Nonnumerical Computation, ed. L. Fox, Chapter 5, pp. 97–154, Pergamon Press.
Ollengren, A. (1976) Definition of Programming Languages by Interpreting Automata,
Academic Press.
Peterson, J.L. (1977) Petri Nets, ACM Computing Surveys 9(3):223–252.
Scott, D.S. (1967) Some Definitional Suggestions for Automata Theory, Journal of
Computer and System Sciences 1(2):187–212.
Tennent, R.D. (1981) Principles of Programming Languages, Prentice-Hall.
Wegner, P. (1972) The Vienna Definition Language, ACM Computing Surveys 4(1):5–
63.
23
3
Simple Expressions and Commands
The S, M, C machine emphasises the idea of computation as a sequence of transitions involving simple data manipulations; further the definition of the transitions falls into simple cases
according to the syntactic structure of the expression or command on top of the control stack.
However, many of the transitions are of little intuitive importance, contradicting our idea of
the right choice of the “size” of the transitions. Further the definition of the transitions is not
syntax-directed so that, for example, the transitions of c; c are not directly defined in terms of
those for c and those for c . Finally but really the most important, the S, M, C machine is
not a formalisation of intuitive operational ideas but is rather, fairly clearly, correct given these
intuitive ideas.
In this chapter we develop a method designed to answer these objections, treating simple
expressions and commands as illustrated by the language L. We consider run-time errors and
say a little on how to establish properties of transition relations. Finally we take a first look at
simple type-checking.
3.1 Simple Expressions
Let us consider first the very simple subset of expressions given by:
e ::= m | e0 + e1
24
and how the S, M, C machine deals with them. For example we have the transition sequence
for the expression (1 + (2 + 3)) + (4 + 5):
ε, M, (1 + (2 + 3)) + (4 + 5) −→ ε, M, (1 + (2 + 3)) (4 + 5) +
−→ ε, M, 1 (2 + 3) + (4 + 5) +
−→ 1, M, (2 + 3) + (4 + 5) +
−→ 1, M, 2 3 + + (4 + 5) +
−→ 2 1, M, 3 + + (4 + 5) +
−→ 3 2 1, M, + + (4 + 5) +
(∗)
−→ 5 1, M, + (4 + 5) +
(∗)
−→ 6, M, (4 + 5) +
−→3 5 4 6, M, + +
(∗)
−→ 9 6, M, +
(∗)
−→ 15, M, ε
In these 13 transitions only the 4 additions marked (∗) are of any real interest as system events.
Further the intermediate structures generated on the stacks are also of little interest. Preferable
would be a sequence of 4 transitions on the expression itself thus:
(1 + (2 + 3)) + (4 + 5)) −→ (1 + 5) + (4 + 5)
−→ 6 + (4 + 5)
−→ 6 + 9
−→ 15
where we are ignoring the memory and we have marked the occurrences of the additions in each
transition. (These transition sequences of expressions are often called reduction sequences (=
derivations) and the occurrences are called redexes; this notation originates in the λ-calculus
(see, e.g., [Hin]).)
Now consider an informal specification of this kind of expression evaluation. Briefly one might
just say one evaluates from left-to-right. More pedantically one could say:
Constants Any constant, m, is already evaluated with itself as value.
Sums To evaluate e0 + e1
(1) Evaluate e0 obtaining m0 , say, as result.
(2) Evaluate e1 obtaining m1 , say, as result.
25
(3) Add m0 to m1 obtaining m2 , say, as result.
This finishes the evaluation and m2 is the result of the evaluation.
Note that this specification is syntax-directed, and we use it to obtain rules for describing steps
(= transitions) of evaluation which we think of as nothing else than a derivation of the form:
e = e1 −→ e2 −→ . . . −→ en−1 −→ en = m
(where m is the result). Indeed if we just look at the first step we see from the above specification
that
(1) If e0 is not a constant the first step of the evaluation of e0 + e1 is the first step of the
evaluation of e0 .
(2) If e0 is a constant, but e1 is not, the first step of the evaluation of e0 + e1 is the first step
of the evaluation of e1 .
(3) If e0 and e1 are constants the first (and last!) step of the evaluation of e0 +e1 is the addition
of e0 and e1 .
Clearly too the first step of evaluating an expression, e, can be taken as resulting in an expression
e with the property that the evaluation of e is the first step followed by the evaluation of e .
We now put all this together to obtain rules for the first step. These are rules for establishing
binary relationships of the form:
e −→ e
≡
e is the result of the first step of the evaluation of e.
Rules: Sum
e0 −→ e0
(1)
e0 + e1 −→ e0 + e1
(2)
e1 −→ e1
m0 + e1 −→ m0 + e1
(3) m0 + m1 −→ m2
(if m2 is the sum of m0 and m1 )
Thus, for example, rule 1 states what is obvious from the above discussion:
If e0 is the result of the first step of the evaluation of e0 then e0 + e1 is the result of the first
step of the evaluation of e0 + e1 .
We now take these rules as a definition of what relationships hold – namely exactly these we
can establish from the rules. We take the above discussion as showing why this mathematical
definition makes sense from an intuitive view; it is the direct formalisation referred to above.
As an example consider the step:
(1 + (2 + 3)) + (4 + 5) −→ (1 + 5) + (4 + 5)
26
To establish this step we have
1. 2 + 3
−→ 5
(By rule 3)
2. 1 + (2 + 3)
−→ 1 + 5
(By rule 2)
3. (1 + (2 + 3)) + (4 + 5) −→ (1 + 5) + (4 + 5)
(By rule 1)
Rather than this unnatural “bottom-up” method we usually display these little proofs in the
“top-down” way they are actually “discovered”. The arrow is supposed to show the “direction”
of discovery:
Sum 1
(1+(2+3))+(4+5) −→ (1+5)+(4+5))
1 + (2+3)
Sum 2
−→ 1 + 5
2+3
Sum 3
−→ 5
✫
✻
✪
Thus, while the evaluation takes four steps, the justification (proof) of each step has a certain
size of its own (which need not be displayed). In this light the S, M, C machine can be viewed
as mixing-up the additions with the reasons why they should be performed into one long linear
sequence.
It could well be argued that our formalisation is not really that direct. A more direct approach
would be to give rules for the transition sequences themselves (the evaluations). For the intuitive
specification refers to these evaluations rather than any hypothetical atomic actions from which
they are composed. However, axiomatising a step is intuitively simpler, and we prefer to follow
a simple approach until it leads us into such difficulties that it is better to consider whole
derivations.
Another point concerns the lack of formalisation of our ideas. The above rules are easily turned
into a formal system of formulae, axioms and rules. What we would want is a sufficiently
elastic conception of a range of such formal systems which on the one hand allows the natural
expression of all the systems of rules we wish, and on the other hand returns some profit in
the form of interesting theorems about such systems or interesting computer systems based on
such systems. However, the present work is too exploratory for us to fix our ideas, although we
may later try out one or two possibilities. We also fear that introducing such formalities could
easily lead us into obscurities in the presentation of otherwise natural ideas.
Now we try out more expressions. To evaluate variables we need the memory component of the
S, M, C machines – indeed that is the only “natural” component they have! It is convenient
27
here to change our notation to a more generally accepted one:
OLD
NEW
Memory
Store
Memories = (Var −→ N) = S
3.1.1
M
σ
M [m/v]
σ[m/v]
L-Expressions
Now for the expression language of L:
e ::= m | v | (e + e ) | (e − e ) | (e ∗ e )
we introduce the configurations
Γ = { e, σ }
and the relation
e, σ −→ e , σ
meaning one step of the evaluation of e (with store σ) results in the expression e (with store
σ). The rules are just those we already have, adapted to take account of stores plus an obvious
rule for printing the value of a variable in a store.
Rules: Sum
(1)
e0 , σ −→ e0 , σ
e0 + e1 , σ −→ e0 + e1 , σ
(2)
e1 , σ −→ e1 , σ
m + e1 , σ −→ m + e1 , σ
28
(3) m + m , σ −→ n, σ
Minus
1,2. Exercise for the reader.
3. m − m , σ −→ n, σ
Times
1,2,3. Exercise for the reader.
Variable
(1) v, σ −→ σ(v), σ
(where n = m + m )
(if m ≥ m and n = m − m )
Note the two uses of the symbol, +, in rule Sum 3: one as a syntactic construct and one for
the addition function. We will often overload symbols in this way relying on the context for
disambiguation. So here, for example, to make sense of n = m+m we must be meaning addition
as the left-hand-side of the equation denotes a natural number.
Of course the terminal configurations are those of the form m, σ , and m is the result of the
evaluation. Note that there are configurations such as:
γ = 5 + (7 − 11), σ
which are not terminal but for which there is no γ with γ −→ γ .
Definition 11 Let Γ, T, −→ be a tts. A configuration γ is stuck if γ ∈ T and ¬∃γ . γ −→ γ .
In most programming languages these stuck configurations result in run-time errors. These will
be considered below.
The behaviour of expressions is the result of their evaluation and is defined by:
eval(e, σ) = m
⇔
e, σ −→∗ m, σ
The reader will see (from 2.3 below, if needed) that eval is a well-defined partial function.
One can also define the equivalence of expressions by:
e≡e
3.1.2
⇔
∀σ. eval(e, σ) = eval(e , σ)
Boolean Expressions
Now we turn to the Boolean expressions of the language L given by:
b := t | b or b | e = e | ∼b
Here we take Γ = { b, σ } and consider the rules for the transition relation. There are clearly
none for truth-values, t, but there are several possibilities for disjunctions, b or b . These possibilities differ not only in the order of the transitions, but even on which transitions occur. The
configurations are pairs b, σ .
29
A. Complete Evaluation: This is just the Boolean analogue of our rules for expressions and
corresponds to the method used by our SMC-machine.
b0 , σ −→ b0 , σ
(1)
b0 or b1 , σ −→ b0 or b1 , σ
(2)
b1 , σ −→ b1 , σ
t or b1 , σ −→ t or b1 , σ
(3) t or t −→ t
(where t = t ∨ t )
B. Left-Sequential Evaluation: This takes advantage of the fact that it is not needed to
evaluate b, in tt or b, as the result will be tt independently of the result of evaluating b,
b0 , σ −→ b0 , σ
(1)
b0 or b1 , σ −→ b0 or b1 , σ
(2) tt or b1 , σ −→ tt, σ
(3) ff or b1 , σ −→ b1 , σ
C. Right-Sequential Evaluation: Like B but “backwards”.
D. Parallel Evaluation: This tries to combine the advantages of B and C by evaluating b0 and
b1 in parallel. In practice that would mean having two processors, one for b0 and one for b1 , or
using one but interleaving, somehow, the evaluations of b0 and b1 . This idea is therefore not
found in the usual sequential programming languages (as opposed to these making explicit
provisions for concurrency). However, it may be useful for hardware specification.
b0 , σ −→ b0 , σ
(1)
b0 or b1 , σ −→ b0 or b1 , σ
(2)
b1 , σ −→ b1 , σ
b0 or b1 , σ −→ b0 or b1 , σ
(3) tt or b1 , σ −→ tt, σ
(4) b0 or tt, σ −→ tt, σ
(5) ff or b1 , σ −→ b1 , σ
(6) b0 or ff, σ −→ b0 , σ
The above evaluation mechanisms are very different when subexpressions can have non-terminating
evaluations, when we have the following relationships:
B⇐A
⇓
⇓
D⇐C
where X ⇒ Y means that if method X terminates with result t, so does method Y. We take
method A for the semantics of our example language L.
30
For Boolean expressions of the form e = e our rules depend on those for expressions, but
otherwise are normal (and for brevity we omit the σ’s).
• Equality
e0 −→ e0
(1)
e0 = e1 −→ e0 = e1
(2)
e1 −→ e1
m = e1 −→ m = e1
(3) m = n −→ t
(where t is tt if m = n and ff otherwise)
For negations ∼b we have, again omitting the σ’s:
• Negation
b −→ b
(1)
∼b −→ ∼b
(2) ∼t −→ t
(where t = ¬t)
The behaviour of Boolean expressions is defined by:
eval(b, σ) = t ⇔ b, σ −→∗ t, σ
One can also define equivalence of Boolean expressions by:
b≡b
3.2
⇔
∀σ. eval(b, σ) = eval(b , σ)
Simple Commands
Again we begin with a trivial language of commands,
c ::= nil | v := e | c; c
31
and see how the SMC-machine behaves on an example:
ε, abc, z := x; (x := y; y := z) −→ ε, abc, z := x x := y; y := z
−→ z, abc, x := x := y; y := z
−→ a z, abc, := x := y; y := z
(∗)
−→ ε, aba, x := y; y := z
−→ ε, aba, x := y y := z
−→2 b x, aba, := y := z
(∗)
−→ ε, bba, y := z
−→2 a y, bba, :=
(∗)
−→ ε, baa, ε
And we see that of the eleven transitions only three – the assignments – are of interest as
system events.
Preferable here would be a sequence of three transitions on configurations of the form c, σ ,
thus:
z := x; (x := y; y := z), abc −→ (x := y; y := z), aba
−→ y := z , bba
−→ baa
where we have marked the assignments occurring in transitions.
Now informally one can specify such command executions as follows:
• Nil: To execute nil from store σ take no action and terminate with σ as the final store of
the execution.
• Assignment: To execute v := e from store σ evaluate e, and if the result is m, change σ to
σ[m/v] (the final store of the execution).
• Composition: To execute c; c from store σ
(1) Execute c from store σ obtaining a final store, σ , say, if this execution terminates.
(2) Execute c from the store σ . The final store of this execution is also the final store of the
execution of c; c .
Sometimes the execution of c; c is pictured in terms of a little flowchart:
✲
c
✲
32
c
✲
As in the case of expressions one sees that this description is syntax-directed. We formalise it
considering terminating executions of a command c from a store σ to be transition sequences
of the form:
c, σ = c0 , σ0 −→ c1 , σ1 −→ . . . −→ cn−1 , σn−1 −→ σn
Here we take the configurations to be:
Γ = { c, σ } ∪ {σ}
and the terminal configurations to be
T = {σ}
where the transition relation c, σ −→ c , σ (resp. σ ) is read as:
One step of execution of the command c from the store σ results in the store σ and the rest
of the execution of c is the execution of c from σ (resp. and the execution terminates).
Thus we choose c to represent, in as simple a way as is available, the remainder of the execution
of c after its first step. The rules are
• Nil: nil, σ −→ σ
• Assignment:
e, σ −→∗ m, σ
(1)
v := e, σ −→ σ[m/v]
• Composition:
c0 , σ −→ c0 , σ
(1)
c0 ; c1 , σ −→ c0 ; c1 , σ
(2)
c0 , σ −→ σ
c0 ; c1 , σ −→ c1 , σ
Note: In formulating the rule for assignment we have considered the entire evaluation of the
right-hand-side as part of one execution step. This corresponds to a change in view of the size
of our step when considering commands, but we could just as well have chosen otherwise.
As an example consider the first transition desired above for the execution
z := x; (x := y; y := z), abc
It is presented in the top-down way
z := x; (x := y; y := z), abc
Ass 1
z := x, abc −→ aba
Var 1
x, abc −→ a, abc
Comp 2
−→
(x := y; y := z), aba
33
Again we see, as in the case of expressions a “two-dimensional” structure consisting of a “horizontal” transition sequence of the events of system significance and for each transition a “vertical” explanation of why and how it occurs.
γ0
γ1
✲
✁❆❆
✁ ❆
✁
❆
✁
❆
✁✁ proof ❆
......
✲
✁❆❆
✁ ❆
✁
❆
✁
❆
✁✁ proof ❆
✲
γn ∈ T
✁❆❆
✁ ❆
✁
❆
✁
❆
✁✁ proof ❆
For terminating executions of c0 ; c1 this will have the form:
c1
c1
c2
< c0 ; c1 , σ >→ · · · → < c0.. ; c1 , σ .. > → < c1 , σ .. >
· · · → σ ..
..
..
..
< c0 , σ >→
→< c0 , σ >
→σ
..
✁❆❆
✁❆❆
✁ ❆
··· ✁
✁ ❆
❆
✁
✁
❆
❆
✁
❆
✁✁
❆
✁
✁
❆
✁❆❆
✁❆❆
✁ ❆ ··· ✁ ❆
✁
❆ ✁
❆
✁
❆✁
❆
✁
✁
✁
✁❆
❆
Again we see that the SMC-machine transition sequences are more-or-less linearisations of these
structures. Note the appearance of rules for binary relations (with additional data components)
such as:
def
R(c, c , σ, σ ) = c, σ −→ c , σ
S(e, e , σ)
def
= e, σ −→ e , σ
Later we shall make extensive use of predicates to treat the context-sensitive aspects of syntax
(= the static aspects of semantics). As far as we can see there is no particular need for ternary
relations, although the above discussion on the indirectness of our formalisation does suggest
the possibility of needing relations of variable degree for dealing with execution sequences.
3.3
L-commands
Recalling the syntax of L-commands,
c ::= nil | v := e | c; c | if b then c else c | while b do c
we see that it remains only to treat conditionals and repetitions.
• Conditionals: To execute if b then c else c from σ
1. Evaluate b in σ
2.1 If result was tt execute c from σ.
34
2.2 If result was ff execute c from σ.
In pictures we have:
❄
✧✧❜❜
❜
✧
✧
❜❜
✧
b
✧
❜❜
✧
❜
✧
✑ ❜
◗
tt ✰
ff
✑
◗
❜✧✧
s❜
◗
✧✑
✧
❜
✧
✧
❜
❜
c
c
❄
❄
And the rules are: ∗
b, σ −→ tt, σ
(1)
if b then c else c , σ −→ c, σ
(2)
b, σ −→∗ ff, σ
if b then c else c , σ −→ c , σ
Note: Again we are depending on the transition relation of another syntactic class – here
Boolean expressions – and a whole computation from that class becomes one step of the computation.
Note: No rules for T (if b then c else c ) are given as that predicate never applies. For a
conditional is never terminal as one always has at least one action – namely evaluating the
condition.
• While: To execute while b do c from σ
1. Evaluate b
2.1 If the result is tt, execute c from σ. If that terminates with final state σ , execute while b do c
from σ .
2.2 If the result is ff, the execution is finished and the final state is σ.
In pictures we have the familiar flowchart:
35
❄
✧✧❜❜
✧
❜
✧
❜❜
✧
b
✧
❜❜
✧
❜
✧
❜
❜✧✧
✲
❄
✻
c
The rules are:
(1)
b, σ −→∗ tt, σ
while b do c, σ −→ c; while b do c, σ
(2)
b, σ −→∗ ff, σ
while b do c, σ −→ σ
Example 12 Consider the factorial example y := 1; w from Chapter 1, where w = while ∼(x =
0) do c where c = (y := y ∗ x; x := x − 1). We start from the state 3, 5 .
y 3, 5
ASS1
−→
COMP2
−→
COMP1
−→
COMP2
−→
COMP2
−→
COMP1
−→
COMP1
−→
w, 3, 1
c; w, 3, 1
(via WHI)
x := x − 1; w, 3, 3
(via COMP2 and ASS1)
w, 2, 3
c; w, 2, 3
(WHI)
x := x − 1; w, 2, 6
(via COMP2 and ASS1)
w, 1, 6
−→
c; w, 1, 6
−→
x := x − 1; w, 1, 6
−→ w, 0, 6
COMP2
−→ 0, 6
(via WHI2)
A terminating execution sequence of a while-loop w = while b do c looks like this (omitting
σ’s):
w −→ c; w −→ . . . −→ w −→ c; w −→ . . . −→ w − − − − − − . . . −→ w −→ ·
b −→∗ tt c −→ . . . −→ ·b −→∗ tt c −→ . . . −→ ·b − − − − − − . . . −→ ·; b −→∗ ff
36
∗
☞☞▲
▲
☞
▲
☞
▲
☞
▲▲
☞
. . .☞☞▲
☞☞▲
▲
☞
☞ ▲
▲☞
▲
☞
▲
▲
☞
☞
▲▲
☞
☞ ▲▲
∗
☞☞▲
▲
☞
▲
☞
▲
☞
▲▲
☞
. . . ☞☞▲ ∗
☞☞▲
▲
☞
☞ ▲
▲ ☞
▲
☞
▲
▲
☞
☞
▲▲
☞
☞ ▲▲
∗
☞☞ ▲▲
☞
☞
☞
▲
▲
☞
∗
☞▲
☞☞▲
☞ ▲
▲
▲ ☞
▲
▲☞
▲
☞▲
☞
▲▲
☞
☞ ▲
One can now define the behaviour and equivalence of commands by:
exec(c, σ) = σ
⇔
c, σ −→∗ σ
and
c
c
⇔
∀σ. exec(c, σ) = exec(c , σ)
where we are using Kleene equality, which means that one side is defined iff the other is, and
in that case they are both equal.
3.4 Structural Induction
Although we have no particular intention of proving very much either about or with our operational semantics, we would like to introduce enough mathematical apparatus to enable us to
establish the truth of such obvious statements as:
if γ ∈ T then for some γ we have γ −→ γ
The standard tool is the principle of Structural Induction (SI). It enables us to prove properties
P (p) of syntactic phrases, and it takes on different forms according to the abstract syntax of the
language. For L we have three such principles, one for expressions, one for Boolean expressions
and one for commands.
Structural Induction for Expressions
Let P (e) be a property of expressions. Suppose that:
(1)
(2)
(3)
(4)
(5)
For all m in N it is the case that P (m) holds, and
For all v in Var it is the case that P (v) holds, and
For all e and e in E if P (e) and P (e ) holds so does P (e + e ), and
As 3 but for −, and
As 3 but for ∗
Then for all expressions e, it is the case that P (e) holds.
37
We take this principle as being intuitively obvious. It can be stated more compactly by using
standard logical notation:
[(∀m ∈ N. P (m)) ∧ (∀v ∈ Var. P (v))
∧ (∀e, e ∈ E. P (e) ∧ P (e ) ⊃ P (e + e ))
∧ (∀e, e ∈ E. P (e) ∧ P (e ) ⊃ P (e − e ))
∧ (∀e, e ∈ E. P (e) ∧ P (e ) ⊃ P (e ∗ e ))]
⊃ ∀e ∈ E. P (e)
As an example we prove
Fact 13 The transition relation for expressions is deterministic.
PROOF. We proceed by SI on the property P (e) where
P (e) ≡ ∀σ, γ , γ ( e, σ −→ γ ∧ e, σ −→ γ ) ⊃ γ = γ
Now there are five cases according to the hypotheses necessary to establish the conclusion by
SI.
1. e = m ∈ N Suppose m, σ −→ γ , γ . But this cannot be the case as m, σ is stuck.
Thus P (e) holds vacuously.
2. e = v ∈ Var Suppose v, σ −→ γ , γ . Then as there is only one rule for variables, we
have γ = σ(v), σ = γ .
3. e = e0 + e1 Suppose e0 + e1 , σ −→ γ , γ . There are three subcases according to why
e0 + e1 , σ −→ γ .
3.1 Rule 1 For some e0 we have e0 , σ −→ e0 , σ and γ = e0 + e1 , σ . Then e0 is not in
N (otherwise e0 , σ would be stuck) and so for some e0 we have e0 , σ −→ e0 , σ and so
γ = e0 + e1 , σ . But by the induction hypothesis applied to e0 we therefore have e0 = e0
and so γ = γ .
3.2 Rule 2 We have e0 = m ∈ N and for some e1 we have e1 , σ −→ e1 , σ and γ = m+
e1 , σ . Then e1 is not in N and for some e1 we have e1 , σ −→ e1 , σ and γ = m + e1 , σ .
But applying the induction hypothesis to e1 , we see that e1 = e1 and so γ = γ .
3.3 Rule 3 We have e0 = m0 , e1 = m1 . Then clearly γ = γ .
4. e = e0 − e1
5. e = e0 ∗ e1 These cases are similar to the third case and are left to the reader.
In the above we did not need such a strong induction hypothesis. Instead we could choose a
fixed σ and proceed by SI on Q(e) where:
Q(e) ≡ ∀γ , γ . ( e, σ −→ γ ∧ e, σ −→ γ ) ⊃ γ = γ
However, this is just a matter of luck (here that the evaluation of expressions does not side
effect the state). Generally it is wise to choose one’s induction hypothesis as strong as possible.
38
The point is that if one’s hypothesis has the form (for example)
P (e) ≡ ∀σ. Q(e, σ)
then when proving P (e0 + e1 ) given P (e0 ) and P (e1 ) one fixes σ and tries to prove Q(e, σ). But
in this proof one is at liberty to use the facts Q(e0 , σ), Q(e0 , σ ), Q(e1 , σ), Q(e1 , σ ) for any σ
and σ .
SI for Boolean Expressions
We just write down the symbolic version for a desired property P (b) of Boolean expressions.
[(∀t ∈ T. P (t)) ∧ (∀e, e ∈ E. P (e = e ))
∧ (∀b, b ∈ B. P (b) ∧ P (b ) ⊃ P (b or b ))
∧ (∀b ∈ B. P (b) ⊃ P (∼b))]
⊃ ∀b ∈ B.P (b)
In general when applying this principle one may need further structural inductions on expressions. For example:
Fact 14 If b is not in T and contains no occurrence of an expression of the form (m − n) where
m < n, then no b, σ is stuck.
PROOF. We fix σ and proceed by SI on Boolean expressions on the property:
Q(b) ≡ [b ∈ T ∧ (∀m < n.(m − n) does not occur in b)]
⊃ b, σ is not stuck
Case 1
b = tt This holds vacuously.
Case 2
b = (e = e ) Here there are three subcases depending on the forms of e and e .
Case 2.1
If e is not in N, then for some e we have e, σ −→ e , σ
Lemma For any expression e not in N if e has no subexpressions of the form, m−n, where
m < n, then no e, σ is stuck.
×
Proof
By SI on expressions and left to the reader. ✷
Continuing with case 2.1 we see that e = e , σ −→ e = e , σ so b, σ is not stuck.
Case 2.2 Here e is in N but e is not; the proof is much like case 2.1 and also uses the
lemma.
Case 2.3
Here e, e are in N and we an use rule EQU. 3.
Case 3 b = (b0 or b1 ) This is like case 3 of the proof of fact 1.
Case 4 b = ∼b0 If b0 is not in T we can easily apply the induction hypothesis. Otherwise
use rule NEG. 2.
39
This concludes all the cases and hence the proof.
SI for Commands
We just write down the symbolic version for a (desired) property P (c) of commands:
[P (nil) ∧ ∀v ∈ Var, e ∈ E. P (v := e)
∧ (∀c, c ∈ C. P (c) ∧ P (c ) ⊃ P (c; c ))
∧ (∀b ∈ B.∀c, c ∈ C. P (c) ∧ P (c ) ⊃ P (if b then c else c ))
∧ (∀b ∈ B. ∀c ∈ C. P (c) ⊃ P (while b do c))]
⊃ ∀c ∈ C. P (c)
For an example we prove:
Fact 15 If v does not occur on the left-hand-side of an assignment in c, then the execution of
c cannot affect its value. That is if c, σ −→∗ σ then σ(v) = σ (v).
PROOF. By SI on commands. The statement of the hypothesis should be apparent from the
proof, and is left to the reader.
Case 1 c = nil Clear.
Case 2 c = (v := e) Here v = v and we just use the definition of σ[m/v ].
Case 3 c = (c0 ; c1 ) Here if c0 ; c1 , σ −→∗ σ then for some σ we have c0 , σ −→∗ σ and
c1 , σ −→∗ σ . (This requires a lemma for proof by the reader).
Then by the induction hypothesis applied first to c1 and then to c0 we have:
σ (v) = σ (v) = σ(v)
Case 4 c = if b then c0 else c1 Here we easily use the induction hypothesis on c0 and c1
(according to the outcome of the evaluation of b).
Case 5 c = while b do c Here we argue on the length of the transition sequence c, σ −→
. . . −→ σ . This is just an ordinary mathematical induction. In case the sequence has length
0, we have σ = σ. Otherwise there are two cases according to the result of evaluating b. We
just look at the harder one.
Case 5.1 c, σ −→ c ; c, σ −→ . . . −→ σ1 . Here we see that c , σ −→∗ σ2 (and apply
the main SI hypothesis) and also that c, σ2 −→∗ σ1 and a shorter transition sequence to
which the induction hypothesis can therefore be applied.
This particular lemma shows that on occasion we will use other induction principles such as
induction on the length of a derivation sequence.
Another possibility is to use induction on some measure of the size of the proof of an assertion
γ −→ γ (which would, strictly speaking, require a careful definition of the size measure).
40
Anyway we repeat that we will not develop too much “technology” for making these proofs,
but would like the reader to be able, in principle, to check out simple facts.
3.5 Dynamic Errors
In the definition of the operational semantics of L-expressions we allowed configurations of the
kind (5 + 7) ∗ (10 − 16), σ to stick. Thus, although we did ensure:
γ ∈ T ⊃ ¬∃γ .γ −→ γ
we did not ensure the converse. Implementations of real programming languages will ensure the
converse generally by issuing a run-time ( = dynamic) error report and forcibly terminating
the computation. It would therefore be pleasant if we could also specify dynamic errors.
As a first approximation we add an error configuration to the possible configurations of each
of the syntactic classes of L. Then we add some error rules.
• Expressions
· Sum
4.
e0 , σ −→ error
e0 + e1 , σ −→ error
5.
e1 , σ −→ error
m + e1 , σ −→ error
· Minus
4,5 as for Sum
6. m − m , σ −→ error
· Times
4,5 as for Sum
• Boolean Expressions
· Disjunction
4,5 as for Sum
· Equality
4,5 as for Sum
· Negation
b, σ −→ error
3.
∼b, σ −→ error
• Commands
· Assignment
e, σ −→ error
2.
v := e, σ −→ error
· Composition
c0 , σ −→ error
3.
c0 ; c1 , σ −→ error
(if m < m )
41
· Conditional
b, σ −→∗ error
if b then c else c , σ −→ error
· Repetition
b, σ −→∗ error
3.
while b do c, σ −→ error
3.
So the only possibility of dynamic errors in L arises from the subtraction of a greater from a
smaller. Of course other languages can provide many other kinds of dynamic errors: division by
zero, overflow, taking the square root of a negative number, failing dynamic type-checking tests,
overstepping array bounds, missing a dangling reference or reaching an uninitialised location
etc. etc. But the above simple example does at least indicate a possibility.
Fact 16 No L-configuration sticks (with the above rules added).
PROOF. Left to the reader as an exercise.
3.6 Simple Type-Checking
We consider a variant, L , of L in which expressions and Boolean expressions are amalgamated
into one syntactic class and have to be sorted out again by type-checking. Here is the language
L.
• Basic Syntactic Sets
· Truth-values: t ∈ T
· Numbers: m, n ∈ N
· Variables: v ∈ Var = {a, b, x, x , x1 , x2 , . . .}
· Binary Operations: bop ∈ Bop = {+, −, ∗, =, or}
• Derived Syntactic Sets
· Expressions: e ∈ Exp where:
e ::= m | t | v | e0 bop e1 | ∼e
· Commands: c ∈ Com where:
c ::= nil | v := e | c0 ; c1 | if e then c0 else c1 | while e do c
Note: We have taken Var to be infinite in the above in order to raise a little problem (later)
on how to avoid infinite memories.
Many expressions such as (tt + 5) or ∼6 now have no sense to them, and nor do such commands
as if x or 5 then c0 else c1 . To make sense an expression must have a type, and in L there are
exactly two possibilities:
42
• Types: τ ∈ Types = {nat, bool}
To see which expressions have types and what they are we will just give some rules for assertions:
e:τ
≡
e has type τ
Note first that the basic syntactic sets have, in a natural way, associated type information.
Clearly we will have truth-values having type bool, numbers having type nat, variables having
type nat and for each binary operation, bop, we have a partial binary function τbop on Types:
+, −, ∗ bool nat
=
bool
nat
bool
?
?
bool
?
?
nat
?
nat
nat
?
bool
• Rules
Truth-values:
t : bool
Numbers:
m : nat
Variables:
v : nat
e0 : τ0 e1 : τ1
Binary Operations:
e0 bop e1 : τ2
e : bool
Negation:
∼e : bool
or
bool nat
bool bool
?
nat
?
?
(where τ2 = τbop (τ0 , τ1 ))
Now for commands we need to sort out those commands which are well-formed in the sense that
all subexpressions have a type and are Boolean when they ought to be. The rules for commands
involve assertions:
Wfc(c) ≡ c is a well-formed command.
Nil:
Wfc(nil)
e : nat
Assignment:
Wfc(v := e)
Wfc(c0 ) Wfc(c1 )
Sequencing:
Wfc(c0 ; c1 )
e : bool Wfc(c0 ) Wfc(c1 )
Conditional:
Wfc(if e then c0 else c1 )
e : bool Wfc(c)
While:
Wfc(while e do c)
Of course all of this is really quite trivial and one could have separated out the Boolean expressions very easily in the first place, as was done with L. However, we will see that the
method generalises to the context-sensitive aspects, also referred to in the literature as the
static semantics.
43
Turning to the dynamic semantics we want now to avoid configurations c, σ with σ : Var −→
N, as such stores are infinite objects. For we have more or less explicitly indicated that we are
doing (hopefully nice) finitary mathematics. The problem is easily overcome by noting that we
only need σ to give values for all the variables in C, and there are certainly only finitely many
such variables. Consequently for any finite subset V of Var we set:
StoresV = V −→ N
and take the configurations also to be indexed by V
ΓE,V = { e, σ | ∃τ. e : τ, Var(e) ⊆ V, σ ∈ StoresV }
ΓC,V = { c, σ | Wfc(c), Var(c) ⊆ V, σ ∈ StoresV } ∪ {σ | σ ∈ StoresV }
where Var(e) is the set of variables occurring in e. The rules are much the same as before,
formally speaking. That is they are the same as before but with the variables and metavariables
ranging over the appropriate sets and an added index. So for example in the rule
Comp 2
c0 , σ −→V σ
c0 ; c1 , σ −→V c1 , σ
it is meant that c0 , c1 (and hence c0 ; c1 ) are well formed commands with their variables all in
V and all of the configurations mentioned in the rule are in ΓC,V .
Equally in the rule
Sum 1
e0 , σ −→V e0 , σ
e0 + e1 , σ −→V e0 + e1 , σ
it is meant that all the expressions e0 , e0 , e0 + e1 , e0 + e1 have a type (which must here be
nat) and all their variables are in V and all the configurations mentioned in the rule are in
ΓE,V . Thus the rules define families of transition relations, −→V ⊆ ΓE,V × ΓE,V for expressions,
−→V ⊆ ΓC,V × ΓC,V for commands.
In the above we have taken the definition of Var(e), the variables occurring in e and also of
Var(c) for granted as it is rather obvious what is meant. However, it is easily given by a so-called
definition by structural induction.
Var(t)
= Var(m) = ∅
Var(v)
= {v}
Var(e0 bop e1 ) = Var(e0 ) ∪ Var(e1 )
Var(∼e)
= Var(e)
With this kind of syntax-directed definition what is meant is that it can easily be shown by
SI that the above equations ensure that for any e there is only one V with Var(e) = V . The
44
definition for commands is similar and is left to the reader, the only point of (very slight)
interest is the definition of Var(v := e).
The definition can also be cast in the form of rules for assertions of the form Var(t) = V .
Var(t) = ∅
Var(m) = ∅
Var(v) = {v}
Var(e0 ) = V0 Var(e1 ) = V1
Binary Operations:
Var(e0 bop e1 ) = V0 ∪ V1
Var(e) = V
Negation:
Var(∼e) = V
Truth-values:
Numbers:
Variables
Exercise: Give rules for the assertion Var(e) ⊆ V .
Finally we have a parametrical form of behaviour. For example for commands we have a partial
function:
Exec : CV × StoresV −→ StoresV
where CV = {c ∈ C | Wfc(c) ∧ Var(c) ⊆ V }, given by:
Exec(c, σ) = σ
≡
c, σ −→∗ σ
3.7 Static Errors
The point here is to specify failures in the type-checking mechanism. Here are some rules for a
very crude specification where one just adds a new predicate Error.
• Binary Operations
Error(e0 )
(1)
Error(e0 bop e1 )
Error(e1 )
(2)
Error(e0 bop e1 )
e0 : τ0 e1 : τ1
(3)
Error(e0 bop e1 )
• Negation
Error(e)
Error(∼e)
• Assignment
Error(e)
(1)
Error(v := e)
e : bool
(2)
Error(v := e)
(if τbop (τ0 , τ1 ) is undefined)
45
• Sequencing
Error(c0 )
(1)
Error(c0 ; c1 )
Error(c1 )
(2)
Error(c0 ; c1 )
• Conditional
Error(e)
(1)
Error(if e then c0 else c1 )
Error(c0 )
(2)
Error(if e then c0 else c1 )
Error(c1 )
(3)
Error(if e then c0 else c1 )
e : nat
(4)
Error(if e then c0 else c1 )
• While
Error(e)
(1)
Error(while e do c)
Error(c)
(2)
Error(while e do c)
e : nat
(3)
Error(while e do c)
3.8 Exercises
Expressions
1. Try out a few example evaluations.
2. Write down rules for the right-to-left evaluation of expressions, as opposed to the left-toright evaluation described above.
3. Write down rules for the parallel evaluation of expressions, so that the following kind of
transition sequence is possible:
(1 + (2 + 3)) + ((4 + 5) + 6) −→ (1 + (2 + 3)) + (9 + 6) −→ (1 + 5) + (9 + 6)
−→ 6 + (9 + 6) −→ 6 + 15 −→ 21
Here one transition is one action of imaginary processors situated just above the leaves
of the expressions (considered as a tree).
4. Note that in the rules if e, σ −→ e , σ then σ = σ. This is the mathematical counterpart of the fact that evaluation of L-expressions produces no side-effects. Rephrase the
rules for L-expressions in terms of relations σ e −→ e where σ e −→ e means that
e, σ −→ e , σ and can be read as “given σ, e reduces to e ”.
46
5. Give rules for “genuine” parallel evaluation where one or more processors as imagined in
3 can perform an action during the same transition. [Hint: Use the idea of exercise 4.]
6.
∗∗
Try to develop a method of axiomatising entire derivation sequences. Can you find any
advantages for this idea?
Boolean Expressions
7. Can you find various kinds of rules analogous to those for or for conjunctions b and b ?
By the way, the left-sequential construct is often advantageous to avoid array subscripts
going out of range as in:
while (i <= n) and a[i] <> x
do
i := i + 3; c
8. Treat the following additions to the syntax
e ::= if b then e0 else e1
b ::= if b0 then b1 else b2
Presumably you will have given rules for the usual sequential conditional. Can you find
and give rules for a parallel conditional analogous to parallel disjunction?
9. Treat the following additions to the syntax which introduce the possibilities of side-effects
in the evaluation of expressions:
e ::= begin c result e
(meaning: execute c then evaluate e) and the assignment expression:
e ::= (v := e)
where the intention is that the value of (v := e) is the value of e but the assignment also
occurs, producing a side-effect in general.
10. Show that the equivalence relations on expressions and boolean expressions are respected
by the program constructs discussed above so that for example:
a) e0 ≡ e0 ∧ e1 ≡ e1
⊃
(e0 + e1 ) ≡ (e0 + e1 )
b) e0 ≡ e0 ∧ e1 ≡ e1
⊃
(e0 − e1 ) ≡ (e0 − e1 )
c) e0 ≡ e0 ∧ e1 ≡ e1
⊃
(e0 = e1 ) ≡ (e0 = e1 )
d) b ≡ b
⊃
∼b ≡ ∼b
47
Commands
11. Give a semantics for the “desk calculator” command
v+ := e
so that the equivalence
(v+ := e)
≡
(v := v + e)
holds (and you can prove it!)
12. Give a semantics for the ALGOL-60 assignment command
v1 := (v2 := . . . (vn := e) . . .)
so that (see exercise 9) the equivalence
(v1 := (v2 := . . . (vn := e) . . .))
≡
(v1 := e)
where e = (v2 := . . . (vn := e) . . .) holds, and you can prove it.
13. Treat the simultaneous assignment
v1 := e1 and . . . and vn := en
where the vi must all be different. Execution of this command consists of first evaluating
all the expressions and then performing the assignments.
14. Treat the following variations on the conditional command:
if b then c | unless b then c |
if b1 then c1
else if b2 then c2
..
.
else if bn then cn
else cn+1
and show they can all be eliminated (to within equivalence) in favour of the ordinary
conditional.
15. Treat the simple iteration command
do e times c
and the following variations on repetitive commands like while b do c:
repeat c until b | until b repeat c | repeat c unless b |
48
loop
c1
when b1 do c1 exit
c2
..
.
when bn do cn exit
cn+1
repeat
where the last construct has n possible exits from the loop.
16. Show that equivalence is respected by the above constructs on commands so that, for
example
a) e ≡ e
⊃
(v := e) ≡ (v := e )
b) c0 ≡ c0 ∧ c1 ≡ c1
⊃
c0 ; c1 ≡ c0 ; c1
c) b ≡ b ∧ c0 ≡ c0 ∧ c1 ≡ c1
d) b ≡ b ∧ c ≡ c
⊃
⊃
if b then c0 else c1 ≡ if b then c0 else c1
while b do c ≡ while b do c
17. Redefine behaviour and equivalence to take account of run-time errors. Do the statements
of exercise 16 remain valid?
18.
∗∗
Try time and space complexity in the present setting. [Hint: Consider configurations of
the form, say, c, σ, t, s where
t = “the total time used so far”
s = “the maximum space used so far”]
There is lots to do. Try finding fairly general definitions, define behaviour and equivalence
(approximate equivalence?) and see which program equivalences preserve equivalence. Try
looking at measures for the parallel evaluation of expressions. Try to see what is reasonable
to incorporate from complexity literature. Can you use the benefits of our structured
languages to make standard simulation results easier/nicer for students?
19.
∗∗
Try exercises 23 and 24 from Chapter 1 again.
20. Give an operational semantics for L, but where only 1 step of the evaluation of an expression or Boolean expression is needed for 1 step of execution of a command. Which of the
two possibilities – the “big steps” of the main text or the “little steps” of the exercise –
do you prefer and why?
49
Proof
21. Let c be any command not involving subexpressions of the form (e−e ) or while loops but
allowing the simple iteration command of exercise 15. Show that any execution sequence
c, σ −→∗ . . . terminates.
22. Establish (for L) the following “arithmetic” equivalences:
e0 + 0
≡
e0
e0 + e1
≡
e1 + e0
e0 + (e1 + e2 )
≡
(e0 + e1 ) + e2
etc
Which ones fail if side-effects are allowed in expressions?
Establish the equivalences:
a) if b then c else c ≡ c
b) if b then if b then c0 else c else if b then c1 else c1 ≡ if b then c0 else c1
c) if b then if b then c0 else c1 else if b then c2 else c3 ≡
if b then if b then c0 else c2 else if b then c1 else c3 .
Which ones remain true if Boolean expressions have side-effects/need not terminate?
23. Establish or refute each of the following suggested equivalences for the language L (and
slight extensions, as indicated):
a) nil; c
≡
≡
c
c; nil
b) c; if b then c0 else c1
c) (if b then c0 else c1 ); c
d) while b do c
≡
e) repeat c until b
≡
≡
if begin c result b then c0 else c1
if b then c0 ; c else c1 ; c
if b then (c; while b do c) else nil
≡
c; while ∼b do c
Type-Checking
24. Make L a little more realistic by adding a type real, decimals, variables of all three types,
and a variety of operators. Allow nat to real conversion, but not vice-versa.
25. Show that if c, σ −→ c , σ and x ∈ Dom(σ)\V ar(c) then σ(x) = σ (x).
26. Show that if c, σ −→ c , σ is a transition within ΓC,V and c, σ −→ c , σ is a
transition within ΓC,V where V ⊆ V then, if σ = σ V , it follows that σ = σ V .
50
27. The static error specification is far too crude. Instead one should have a set M of messages
and a relation:
Error(e, m) ≡ m is a report on an error in e
and similarly for commands. Design a suitable M and a specification of Error for L . Try
to develop a philosophy of what a nice error message should be. See [Hor] for some ideas.
28. How would you treat dynamic type-checking in L ? What would be the new ideas for error
messages (presumably one adds an M (see exercise 27) to the configurations).
3.9
Bibliographical Remarks
The idea of reduction sequences originates in the λ-calculus [Hin] as does the present method
of specifying steps axiomatically where I was motivated by Barendregt’s thesis [Bar1]. I applied
the idea to λ-calculus-like programming languages in [Plo1], [Plo2] and Milner saw how to
extend it to simple imperative languages in [Mil1]. More recently the idea has been applied to
languages for concurrency and distributed systems [Hen1], [Mil2], [Hen2]. The present course
is a systematic attempt to apply the idea as generally as possible. A good deal of progress
has been made on other aspects of reduction and the λ-calculus, a partial survey and further
references can be found in [Ber] and see [Bar2].
Related ideas can be found in work by de Bakker and de Roever. A direct precursor of our
method can be found in the work by Lauer and Hoare [Hoa], who use configurations which have
the rough form s1 , . . . , sn , σ where the si are statements (includes commands). They define
a next-configuration function and the definition is to some extent syntax-directed. The idea of
a syntax-directed approach was independently conceived and mentioned all too briefly in the
work of Salwicki [Sal].
Somewhat more distantly various grammatical (= symbol-pushing too) approaches have been
tried. For example W-grammars [Cle] and attribute grammars [Mad]; although these definitions are not syntax-directed definitions of single transitions it should be perfectly possible to
use the formalisms to write definitions which are. The question is rather how appropriate the
formalisms would be with regard to such issues as completeness, clarity (= readability), naturalness, realism, modularity (= modifiability + extensionality). One good discussion of some
of these issues can be found in [Mar]. For concern with modularity consult the course notes
of Peter Mosses. Our method is clearly intended to be complete and natural and realistic, and
we try to be clear; the only point is that it is quite informal, being normal finite mathematics.
There must be many questions on good choices of formalism. As regards modularity we just
hope that if we get the other things in a reasonable state, then current ideas for imposing
modularity on specifications will prove useful.
For examples of good syntax-directed English specifications consult the excellent article by
51
Ledgard on ten mini-languages [Led]. These languages will provide you with mini-projects
which you should find very useful in understanding the course, and which could very well be
the basis for more extended projects. For a much more extended example see the ALGOL 68
Report [Wij]. Structural Induction seems to have been introduced to Computer Science by
Burstall in [Bur]; for a system which performs automatic proofs by Structural Induction on
lists see [Boy]. For discussions of what error messages should be see [Hor] and for remarks on
how and whether to specify them see [Mar].
4
Bibliography
[Bar1]
Barendregt, H. (1971) Some Extensional Term Models for Combinatory Logic and
Lambda-calculi, PhD thesis, Department of Mathematics, Utrecht University.
[Bar2] Barendregt, H. (1981) The Lambda Calculus, Studies in Logic 103, North-Holland.
[Ber]
Berry, G. and L´evy, J-J. A Survey of Some Syntactic Results in the Lambda-calculus,
Proc. MFCS’79, ed. J. Becv´ar, LNCS 74, pp. 552–566.
[Boy]
Boyer, R.S. and Moore, J.S. (1979) A Computational Logic, Academic Press.
[Bur]
Burstall, R.M.B. (1969) Proving Properties of Programs by Structural Induction, Computer Journal 12(1):41–48.
[Cle]
Cleaveland, J.C. and Uzgalis, R.C. (1977) Grammars for Programming Languages,
Elsevier.
[Hen1] Hennessy, M.C.B. and Plotkin, G.D. (1979) Full Abstraction for a Simple Parallel
Programming Language, Proc. MFCS’79, ed. J. Becv´ar, LNCS 74, pp. 108–120.
[Hen2] Hennessy, M.C.B., Li, Wei and Plotkin, G.D. (1981) A First Attempt at Translating
CSP into CCS, Proc. ICDCS’81, pp. 105–115, IEEE.
[Hin]
Hindley, J.R., Lercher, B. and Seldin, J.P. (1972) Introduction to Combinatory Logic,
Cambridge University Press.
[Hoa] Hoare, C.A.R. and Lauer, P.E. (1974) Consistent and Complementary Formal Theories
of the Semantics of Programming Languages, Acta Informatica 3:135–153.
[Hor]
Horning, J.J. (1974) What the Compiler Should Tell The User, Compiler Construction:
An Advanced Course, eds F.L. Bauer and J. Eickel, LNCS 21, pp. 525–548.
[Lau]
Lauer, P.E. (1971) Consistent Formal Theories of The Semantics of Programming
Languages, PhD thesis, Queen’s University of Belfast, IBM Laboratories Vienna TR
25.121.
[Led]
Ledgard, H.F. (1971) Ten Mini-Languages: A Study of Topical Issues in Programming
Languages, ACM Computing Surveys 3(3):115–146.
[Mad] Madsen, O.L. (1980) On Defining Semantics by Means of Extended Attribute Grammars, Semantics-Directed Compiler Generation, ed. N.D. Jones, LNCS 94, pp. 259–299.
[Mar] Marcotty, M., Ledgard, H.F. and von Bochmann, G. (1976) A Sampler of Formal
Definitions, ACM Computing Surveys 8(2):191-276
[Mil1] Milner, A.J.R.G. (1976) Program Semantics and Mechanized Proof, Foundations of
Computer Science II, eds K.R. Apt and J.W. de Bakker, Mathematical Centre Tracts
82, pp. 3–44.
52
[Mil2]
[Plo1]
[Plo2]
[Sal]
[Wij]
Milner, A.J.R.G. (1980) A Calculus of Communicating Systems, LNCS 92.
Plotkin, G.D. (1975) Call-by-name, Call-by-value and the Lambda-calculus, Theoretical
Computer Science 1(2):125–159.
Plotkin, G.D. (1977) LCF Considered as a Programming Language, Theoretical Computer Science 5(3):223–255.
Salwicki, A. (1976) On Algorithmic Logic and its Applications, Mathematical Institute,
Polish Academy of Sciences.
van Wijngaarden, A., Mailloux, B.J., Peck, J.E.L., Koster, C.H.A., Sintzoff, M., Lindsey, C.H., Meertens, L.G.T. and Fisker, R.G. (1975) Revised Report on the Algorithmic
Language ALGOL 68, Acta Informatica 5:1–236.
53
5
Definitions and Declarations
5.1 Introduction
In this chapter we begin the journey towards realistic programming languages by considering
binding mechanisms which enable the introduction of new names in local contexts. This leads to
definitions of local variables in applicative languages and declarations of constant and variable
identifiers in imperative languages. We will distinguish the semantic concepts of environments
and stores. The former concerns those aspects of identifiers which do not change throughout
the evaluation of expressions or the execution of commands and so on; the latter concerns those
aspects which do as in side-effects in the evaluation of expressions or the effects of the execution
of commands. In the static semantics context-free methods no longer suffice, and we show how
our rules enable the context-sensitive aspects to be handled in a natural and syntax-directed
way.
5.2 Simple Definitions in Applicative Languages
We consider a little applicative (= functional) language with simple local definitions of variables.
It can be considered as a first step towards full-scale languages like ML [Gor].
• Syntax Basic Sets
Numbers:
m, n ∈ N
Binary Op.: bop ∈ Bop = {+, −, ∗}
Variables:
x, y, z ∈ Var = {x1 , x2 , . . .}
• Derived Sets
Expressions: e ∈ Exp where
e ::= m | x | e0 bop e1 | let x = e0 in e1
Note: Sometimes let x = e0 in e1 is written instead as e1 where x = e0 . From the point of
view of readability the first form is preferable when a bottom-up style is appropriate, and the
second where a top-down style is appropriate. For in the first case one first defines x and then
uses it, and in the second it is used before being defined.
Clearly any expression contains various occurrences of variables, and in our language there are
two kinds of these occurrences. First we have defining occurrences where variables are introduced; second we have applied occurrences where variables are used. For example, considering
the figure below the defining occurrences are 2, 6, 9 and the others are applied. In some languages - but not ours! - one finds other occurrences which can fairly be termed useless.
54
x1 ∗ ( let x2 = 5 ∗ y 3 ∗ x4
in x5 + ( let y 6 = 14 − x7
in y 8 + ( let x9 = 3 + x10 + x11
in x12 ∗ y 13 )))
Some Variable Occurrences
Now the region of program text over which defining occurrences have an influence is known as
their scope. One often says, a little loosely, that, for example, the scope of the first occurrence
of x in e = let x = e0 in e1 is the expression e1 . But then one considers examples such as
that of the above figure, where occurrence 12 is not in the scope of 2 (as it is instead in the
scope of 9), this is called a hole in the scope of 2. It is more accurate to say that the scope
of a defining occurrence is a set of applied occurrences. In the case of let x = e0 in e1 the
scope of x is all those applied occurrences of x in e1 , which are not in the scope of any defining
occurrence of x in e1 . Thus in the case of figure 1 we have the following table showing which
applied occurrences are in the scope of which defining occurrences (equivalently which defining
occurrences bind which applied occurrences).
Defining Occurrence Applied Occurrences
2
{5, 7, 10, 11}
6
{8, 13}
9
{12}
Note that each applied occurrence is in the scope of at most one defining occurrence. Those
not in any scope are termed free (versus bound); for example occurrences 1, 3, 4 above are free.
One can picture the bindings and the free variables by means of a drawing with arrows such
as:
let x = 5 + y
✸
✑
✑
✑
✻
✻
in let y = 4 + x + y
❙
♦
❙
❙
in x + y + z
✸
✑
✑
✑
✸
✑
✑
✑
From the point of view of semantics it is irrelevant which identifiers are chosen just so long as
the same set of bindings is generated. (Of course a sensible choice of identifiers greatly affects
readability, but that is not a semantic matter.) All we really need are the arrows, but it is
55
hard to accommodate then into our one-dimensional languages. In the literature on λ-calculus
one does find direct attempts to formalise the arrows and also attempts to eliminate variables
altogether; as in Combinatory Logic [Hin]; in Dataflow one sees graphical languages where the
graphs display the arrows [Ack].
Static Semantics
Free Variables: The following definition by structural induction is of FV(e), the set of free
variables (= variables with free occurrences) of e:
m
FV
∅
x
e0 bop e1
let x = e0 in e1
{x} FV(e0 ) ∪ FV(e1 ) FV(e0 ) ∪ (FV(e1 )\{x})
Example 17
FV(let x = 5 + y in ( let y = 4 + y + z in x + y + z))
= FV(5 + y) ∪ (FV( let y = 4 + x + y in x + y + z)\{x})
= {y} ∪ (({x, y} ∪ ({x, y, z}\{y}))\{x})
= {y} ∪ ({x, y, z}\{x})
= {y, z}
Dynamic Semantics
For the most part applicative languages have no concept of state; there is only the evaluation
of expressions in different environments (= semantic contexts). We take:
EnvV = (V −→ N)
for any finite subset of V of the set Var of variables and let ρ range over Env = V EnvV and
write ρ : V to mean that ρ is in EnvV . Of course EnvV = StoreV , but we introduce a new
notation in order to emphasise the new idea.
The set of configurations is also parameterised on V and
ΓV = {e ∈ Exp | FV(e) ⊆ V }
TV = N
56
The transition relation is now relative to an environment and for any ρ : V and e, e in ΓV we
write
ρ
V
e −→ e
and read that in (= given) environment ρ one step of the evaluation of the expression e results
in the expression e . The use of the turnstile is borrowed from formal logic as we wish to think
of the above as an assertion of e −→ e conditional on ρ which in turn is thought of as an
assertion supplied by the environment on the values of the free variables of e and e . As this
environment will not change from step to step of the evaluation of an expression, we will often
use, fixing ρ in the transition relation, the transitive reflexive closure ρ V e −→∗ e . It is left
to the reader to define relative transition systems.
Rules:
Variables:
ρ V x −→ ρ(x)
Binary Operations: (1) ρ V e0 −→ e0 ⇒ ρ V e0 bop e1 −→ e0 bop e1
(2) ρ V e1 −→ e1 ⇒ ρ V m bop e1 −→ m bop e1
(3) ρ V m bop m −→ n
(where n = m bop m )
Note: To save space we are using an evident horizontal lay-out for our rules. That is the rule:
A1 . . . . . . Ak
A
can alternatively be written in the form
A1 , . . . . . . , Ak ⇒ A.
Definition 18 Informally, to evaluate e = let x = e0 in e1 given ρ
(1) Evaluate e0 given ρ to get the value m0 .
(2) Change from ρ to ρ = ρ[m0 /x].
(3) Evaluate e1 given ρ to get the value m.
Then m is the value of e given ρ.
The rules for one step of the evaluation are:
ρ V e0 −→ e0
ρ V let x = e0 in e1 −→ let x = e0 in e1
ρ[m/x] V ∪{x} e1 −→ e1
(2)
ρ V let x = m in e1 −→ let x = m in e1
(3) ρ V let x = m in n −→ n
(1)
Of course these rules are just a clearer version of those given in Chapter 2 for expressions (as
suggested in exercise 4). Continuing the logical analogy our rules look like a Gentzen system
57
of natural deduction [Pra] written in a linear way. Possible definitions of behaviour are left to
the reader.
5.3 Compound Definitions
In general it is not convenient just to repeat simple definitions, and so we consider several ways
of putting definitions together. The category of expressions is now:
e ::= m | x | e0 bop e1 | let d in e
where d ranges over the category Def of definitions where:
d ::= nil | x = e | d0 ; d1 | d0 and d1 | d0 in d1
To understand this it is convenient to think in terms of import and export. An expression, e,
imports values for its free variables from its environment (and produces a value). This can be
pictured as:
◗
◗
◗
◗
◗
x ✲
e
◗
✑
✑
✑
✑
✑
✲
✑
An Expression
where x is a typical free variable of e. A definition, d, imports values for its free variables and
exports values for its defining variables (those with defining occurrences). This can be pictured
as:
x✲
d
y ✲
A Definition
These are dataflow diagrams and they also help explain compound expressions and definition.
For example a definition block let d in e imports from its environment into d and then d exports
into e with any other needed imports of e coming from the block environment. Pictorially
58
a✲
✲ x
d
b ✲.
✲
c✲
✲
◗
◗
◗
◗
◗
e ✑◗
✑
✑
✑
✑
✑
✲ y
✲
A Definition Block
Here a is a typical variable imported by d but not e, and b is one imported by d and e, and
c is one imported by e and not d; again x is a variable exported by d and not imported by e
(useless but logically possible), and y is a variable exported by d and imported by e. Of course
we later give a precise explanation of all this by formal rules of an operational semantics.
Turning to compound definitions we have sequential definition, d0 ; d1 , and simultaneous definitions, d0 and d1 , and private definitions, d0 in d1 . What d0 ; d1 does is import from the
environment into d0 and export from d0 into d1 (with any additional exports needed for d1
being taken from the environment); then d0 ; d1 exports from both d0 and d1 with the latter
taking precedence for common exports. Pictorially (and we need a picture!):
a ✲
✲
b
d1
y
✲
✲
✲
✲ z
✲
d0
c
✲ x
✲ y
✲ z
✲ u
✲
Sequential Definition
Simultaneous definition is much simpler; d0 and d1 imports into both d0 and d1 from the
environment and then exports from both (and there must be no common defined variable).
Pictorially
59
a
✲
d0
✲
x
d1
✲
y
✲
b
✲
.
✲
c
✲
Simultaneous Definition
Finally, a private definition d0 in d1 is just like a sequential one, except that the only exports
are from d1 . It can be pictured as:
a ✲
✲
b
✲
y
z
✲
d1
✲ x
✲ y
d0
c
u
✲
Private Definition
We may write also d0 in d1 as let d0 in d1 or as private d0 within d1 . Private definitions
provide examples of blocks where the body is a definition. We have already seen blocks with
expression bodies and will see ones with command bodies. Tennent’s Principle of Qualification
says that in principle any semantically meaningful syntactic class can be the body of a block
[Ten]. We shall later encounter other examples of helpful organisational principles.
As remarked in [Ten] many programming languages essentially force one construct to do jobs
better done by several; for instance it is common to try to get something of the effect of both
sequential and simultaneous definition. A little thought should convince the reader that there
are essentially just the three interesting ways of putting definitions together.
Example 19 Consider the expression
let x = 3
in let x = 5 & y = 6 ∗ x
in x + y
60
Depending on whether & is ; or and or in, the expression has the values 35 = 5 + (6 ∗ 5) or
23 = 5 + (6 ∗ 3) or 33 = 3 + (6 ∗ 5).
Static Semantics
We will define the set DV(d) of defined variables of a definition d and also FV(d/e), the set of
free variables of a definition d or expression e.
nil x = e
DV
∅
FV
∅
{x}
d0 ; d1
d0 and d1
d0 in d1
DV(d0 ) ∪ DV(d1 )
DV(d0 ) ∪ DV(d1 )
DV(d1 )
FV(e) FV(d0 ) ∪ (FV(d1 )\DV(d0 )) FV(d0 ) ∪ FV(d1 ) FV(d0 ) ∪ (FV(d1 )\DV(d0 ))
For expressions the definition of free variables is the same as before except for the case
FV(let d in e) = FV(d) ∪ (FV(e)\DV(d))
Because of the restriction on simultaneous definitions not all expressions or definitions are wellformed - for example consider let x = 3 and x = 6 in x. So we also define the well-formed ones
by means of rules for a predicate W(d/e) on definitions and expressions.
Rules:
• Definitions
Nil:
Simple:
Sequential:
Simultaneous:
Private:
• Expressions
Constants:
Variables:
Binary Op.:
W(nil)
W(e) ⇒ W(x = e)
W(d0 ), W(d1 ) ⇒ W(d0 ; d1 )
W(d0 ), W(d1 ) ⇒ W(d0 and d1 )
W(d0 ), W(d1 ) ⇒ W(d0 in d1 )
W(m)
W(x)
W(e0 ), W(e1 ) ⇒ W(e0 bop e1 )
61
(if DV(d0 ) ∩ DV(d1 ) = ∅)
Definitions:
W(d), W(e) ⇒ W(let d in e)
Dynamic Semantics
It is convenient to introduce some new notation to handle environments. For purposes of displaying environments consider, for example, ρ : {x, y, z}, where ρ(x) = 1, ρ(y) = 2, ρ(z) = 3.
We will also write ρ as {x = 1, y = 2, z = 3} and drop the set brackets when desired; this
situation makes it clearer that environments can be thought of as assertions.
Next for any V0 , V1 and ρ0 :V0 , ρ1 :V1 we define ρ = ρ0 [ρ1 ]:V0 ∪ V1 by:
ρ1 (x)
ρ(x) =
ρ (x)
0
(x ∈ V1 )
(x ∈ V0 \V1 )
We now have the nice ρ[x = m] to replace the less readable ρ[m/x]. Finally for any ρ0 :V0 , ρ1 :V1
with V0 ∩ V1 = ∅ we write ρ0 , ρ1 for ρ0 ∪ ρ1 . Of course this is equal to ρ0 [ρ1 ], and also to ρ1 [ρ0 ],
but the extra notation makes it clear that it is required that V0 ∩ V1 = ∅.
The expression configurations are parameterised on V by:
ΓV = {e | W(e), FV(e) ⊆ V }
and of course
TV = N
And our transition relation, ρ
V
e −→ e , is defined only for ρ : V , and e, e in ΓV .
For definitions the idea is that just as an expression is evaluated to yield values so is a definition
elaborated to yield a “little” environment (for its defined variables). For example, given ρ =
{x = 1, y = 2, z = 3} the definition x = 5 + x + z; y = x + y + z is elaborated to yield
{x = 9, y = 14}. In order to make this work we add another clause to the definition of Def
d ::= ρ
What this means is that the abstract syntax of declaration configurations allows environments;
it does not mean that the abstract syntax of declarations does so.
In a sense we slipped a similar trick in under the carpet when we allowed numbers as expressions.
Strictly speaking we should only have allowed literals and then allowed natural numbers as part
of the configurations and given rules for evaluating literals to numbers. Similar statements hold
for other kinds of literals. However, there seemed little point in forcing the reader through this
tedious procedure.
62
Returning to definitions we now add clauses for free and defined variables:
FV(ρ) = ∅
DV(ρ) = V
(if ρ : V )
and also add for any ρ that W(ρ) holds, and for any V that
ΓV = {d | W(d), FV(d) ⊆ V }
and
TV = {ρ}
and consider for V and ρ : V and d, d ∈ ΓV the transition relation
ρ
V
d −→ d
which means that, given ρ, one step of the elaboration of d yields d .
Example 20 We shall expect to see that:
x = 1, y = 2, z = 3
x = (5 + x) + z; y = (x + y) + z
−→∗ {x = 9}; y = (x + y) + z
−→∗ {x = 9}; {y = 14}
−→ {x = 9, y = 14}
Rules:
• Expressions: As before but with a change for definitions:
Definitions: Informally, to evaluate e1 = let d in e0 in the environment ρ
(1) Elaborate d in ρ yielding ρ0 .
(2) Change from ρ to ρ = ρ[ρ0 ].
(3) Evaluate e0 in ρ yielding m.
Then the evaluation of e1 yields m. Formally we have:
ρ V d −→ d
(1)
ρ V let d in e −→ let d in e
(2)
ρ
V
ρ[ρ0 ] V ∪V0 e −→ e
let ρ0 in e −→ let ρ0 in e
(where ρ0 : V0 )
(3) ρ V let ρ0 in m −→ m
• Definitions: The first two cases are self-explanatory.
Nil:
ρ V nil −→ ∅
Simple:
(1) ρ V e −→ e ⇒ ρ V x = e −→ x = e
(2) ρ V x = m −→ {x = m}
63
Sequential:
Informally to elaborate d0 ; d1 given ρ
(1) Elaborate d0 in ρ yielding ρ0
(2) Elaborate d1 in ρ[ρ0 ] yielding ρ1
Then the elaboration of d0 ; d1 yields ρ0 [ρ1 ]. Formally we have:
ρ V d0 −→ d0
(1)
ρ V d0 ; d1 −→ d0 ; d1
(2)
ρ[ρ0 ] V ∪V0 d1 −→ d1
ρ V ρ0 ; d1 −→ ρ0 ; d1
(where ρ0 : V0 )
(3) ρ V ρ0 ; ρ1 −→ ρ0 [ρ1 ]
Simultaneous: Informally to elaborate d0 and d1 given ρ
(1) Elaborate d0 in ρ yielding ρ0 .
(2) Elaborate d1 in ρ yielding ρ1 .
Then the elaboration of d0 and d1 yields ρ0 , ρ1 if that is defined. Formally
(1) ρ V d0 −→ d0 ⇒ ρ V d0 and d1 −→ d0 and d1
(2) ρ V d1 −→ d1 ⇒ ρ V ρ0 and d1 −→ ρ0 and d1
(3) ρ V ρ0 and ρ1 −→ ρ0 , ρ1
Private:
Informally to elaborate d0 in d1 given ρ
(1) Elaborate d0 in ρ yielding ρ0 .
(2) Elaborate d1 in ρ[ρ0 ] yielding ρ1 .
Then the elaboration of d0 in d1 yields ρ1 . Formally
(1) ρ V d0 −→ d0 ⇒ ρ V d0 in d1 −→ d0 in d1
(2) ρ[ρ0 ] V ∪V0 d1 −→ d1 ⇒ ρ V ρ0 in d1 −→ ρ0 in d1
(where ρ0 : V0 )
(3) ρ V ρ0 in ρ1 −→ ρ1
Example 21
x = 1, y = 2, z = 3
x = (5 + x) + z; y = (x + y) + z
SEQ1
−→ x = (5 + 1) + z; y = (x + y) + z (using SIM1)
SEQ1
−→ x = 9; y = (x + y) + z
SEQ1
−→ {x = 9}; y = (x + y) + z
(using SIM1)
(using SIM2)
SEQ2
−→ {x = 9}; y = (9 + y) + z
SEQ2
−→ {x = 9}; {y = 14}
SEQ3
−→ {x = 9, y = 14}.
The reader is encouraged here (and generally too) to work out examples for all the other
constructs.
64
5.4 Type-Checking and Definitions
New problems arise in static semantics when we consider type-checking and definitions. For
example one cannot tell whether or not such an expression as x or tt or x + x is well-typed
without knowing what the type of x is and that depends on the context of its occurrence. We
will be able to solve these problems by introducing static environments α to give this type
information and giving rules to establish properties of the form
α
V
e:τ
As usual we work by considering an example language.
• Basic Sets
Types:
Numbers:
Truth-values:
Variables:
Binary Operations:
• Derived Sets
Constants:
Definitions:
τ ∈ Types = {nat, bool}
m, n ∈ N;
t ∈ T;
x, y, z ∈ Var;
bop ∈ Bop = {+, −, ∗, =, or}.
con ∈ Con where con ::= m | t
d ∈ Def where
d ::= nil | x : τ = e | d0 ; d1 | d0 and d1 | d0 in d1
Expressions:
e ∈ Exp where
e ::= con | x | ∼e | e0 bop e1 | if e0 then e1 else e2 |
let d in e
Static Semantics
The definitions of DV(d) and FV(d) are as before as is FV(e) just adding that
FV(if e0 then e1 else e2 ) = FV(e0 ) ∪ FV(e1 ) ∪ FV(e2 )
We now need type environments over V . These form the set
TEnvV = V −→ Types
and the set TEnvV = V TEnvV is ranged over by α and β and we write α : V for α ∈ TEnvV .
Of course all the notation α[β] and α, β extends without change from ordinary environments
to type environments.
65
Now for every V and α:V , τ and e with FV(e) ⊆ V we give rules for the relation
α
V
e:τ
meaning that given α the expression e is well-formed and has type τ . This will involve us in
giving similar rules for constants and also for every V and α : V , β and definition d with
FV(d) ⊆ V , for the relation
α
V
d:β
meaning that given α the definition d is well-formed and yields the type environment β.
Example 22 (1) y = bool (let x : nat = 1 in (x = x) or y) : bool
(2) y = bool (x : nat = if y then 0 else 1; y : nat = x + 1) : {x = nat, y = nat}
Rules:
• Constants:
Numbers:
Truth-values:
• Expressions:
Constants:
Variables:
Negation:
α
α
α
α
α
Binary Operations:
Conditional:
α
V
V
m : nat
t : bool
con : τ ⇒ α V con : τ
(this makes sense!)
x
:
α(x)
V
⇒ α V ∼e : bool
V e : bool
α V e0 : τ0 α V e1 : τ1
(if τ = τ0 τbop τ1 )
α V e0 bop e1 : τ
V
V
e0 : bool, α
V
e1 : τ, α
V
e2 : τ
⇒ α V if e0 then e1 else e2 : τ
α V d : β α[β] V ∪V0 e : τ
(where β : V0 )
α V let d in e : τ
Definition:
Note that this allows the type of variables to be redefined.
Definition 23
Nil:
Simple:
Sequential:
Simultaneous:
Private:
α
α
V
V
α
α
α
α
nil : ∅
e : τ ⇒ α V (x : τ = e) : {x = τ }
α[β0 ] V ∪V0 d1 : β1
V d0 : β0
(where β0 : V0 )
α V (d0 ; d1 ) : β0 [β1 ]
α V d1 : β1
V d0 : β0
(if DV(d0 ) ∩ DV(d1 ) = ∅)
V (d0 and d1 ) : β0 , β1
α[β0 ] V ∪V0 d1 : β1
V d0 : β0
(where β0 : V0 )
α V (d0 in d1 ) : β1
66
It is hoped that these rules are self-explanatory. It is useful to define for any V and α : V and
e with FV(e) ⊆ V the property of being well-formed
WV (e, α) ≡ ∃τ. α
e:τ
V
and also for any V , α : V and d with FV(d) ⊆ V the property of being well-formed
WV (d, α) ≡ ∃β. α
V
d : β.
Dynamic Semantics
If x has type τ in environment α then in the corresponding ρ it should be the case that ρ(x)
also has type τ ; that is if τ = nat, then we should have ρ(x) ∈ N and otherwise ρ(x) ∈ T. To
this end for any V and α : V and ρ : V −→ N + T we define:
ρ : α ≡ ∀x ∈ V. (α(x) = nat ⊃ ρ(x) ∈ N)
∧ (α(x) = bool ⊃ ρ(x) ∈ T)
and put Envα = {ρ : V −→ N + T | ρ : α}. Note that if ρ0 : α0 and ρ1 : α1 then ρ0 [ρ1 ] : α0 [α1 ]
and so too that (if it makes sense) (ρ0 , ρ1 ) : (α0 , α1 ).
Configurations: We separate out the various syntactic categories according to the possible
type environments.
• Expressions: For every α : V we put Γα = {e | WV (e, α)} and Tα = N + T.
• Definitions: We add the production d ::= ρ as before (but with ρ ranging over the Envα )
and then for every α : V we put Γα = {d | WV (d, α)} and Tα = {ρ}.
Transition Relations:
• Expressions: For every α : V we have the relation where ρ : α and e, e ∈ Γα :
ρ
α
e −→ e
• Definitions: For every α : V we have the relation where ρ : α and d, d ∈ Γα :
ρ
α
d −→ d
Rules: The rules are much as usual but with the normal constraints that all mentioned expressions and definitions be configurations and environments be of the right type-environment.
Here are three examples which should make the others obvious.
• Expressions:
Definition 2:
ρ
α
ρ[ρ0 ] α[α0 ] e −→ e
let ρ0 in e −→ let ρ0 in e
67
(where ρ0 : α0 )
• Definitions:
Simple 2:
ρ
Sequential 2:
α x = con −→ {x = con}
ρ[ρ0 ] α[α0 ] d1 −→ d1
ρ α ρ0 ; d1 −→ ρ0 ; d1
(where ρ0 : α0 )
Example 24
{x = tt, y = 5}
{x=bool,y=nat}
let private(x : nat = 1 and y : nat = 2)
within z : nat = x + y
in if x then y + z else y
−→3
let private {x = 1, y = 2}
within z : nat = x + y
in if x then y + z else y
−→4
let private {x = 1, y = 2}
within {z = 3}
in if x then y + z else y
−→
let {z = 3} in if x then y + z else y
−→2
let {z = 3} in y + z
−→4
8.
Declarations in Imperative Languages
The ideas so far developed transfer to imperative languages where we will speak of declarations
(of identifiers) rather than definitions (of variables). Previously we have used stores for imperative languages and environments for applicative ones, although mathematically they are the
same - associations of values to identifiers/variables. It now seems appropriate, however, to use
both environments and stores; the former shows what does not vary and the latter what does
vary when commands are executed.
It is also very convenient to change the definitions of stores by introducing an (arbitrary) infinite
set, Loc, of locations (= references = cells) and taking for any L ⊆ Loc
StoresL = L −→ Values
and
Stores =
StoresL
( = Loc −→fin Values)
L
68
and putting
Env = ld −→fin (Values + Loc)
The idea is that if in some environment ρ we have an identifier x whose values should not
vary then ρ(x) = that value; otherwise ρ(x) is a location, l, and given a store σ : L (with l
in L) then σ(l) is the value held in the location l (its contents). In the first case we talk of
constant identifiers and in the second we talk of variable identifiers. The former are introduced
by constant declarations like
const x = 5
and the latter by variable declarations like
var x = 5
In all cases declarations will produce new (little) environments, just as before. The general form
of transitions will be:
ρ
l
d, σ −→ d , σ
where ρ is the elaboration environment and σ, σ are the stores. So, for example we will have
ρ
l
const x = 5, σ −→ {x = 5}, σ
ρ
l
var x = 5, σ −→ {x = l}, σ[l = 5]
and
(∗)
where l is a certain “new” location.
Locations can be thought of as “abstract addresses” where we do not really want to commit
ourselves to any machine architecture, but only to the needed logical properties. A better way
to think of a location is as an individual or object which has lifetime (= extent); it is created
in a transition such as (∗) and its lifetime continues either throughout the entire computation
(execution sequence) or until it is deleted (= disposed of) (the deletion being achieved either
through such mechanisms as block exit or through explicit storage management primitives
in the language). Throughout its lifetime it has a (varying) contents, generally an ordinary
mathematical value (or perhaps other locations). It is generally referred to by some identifier
and is then said to be the L-value (or left-hand value) of the identifier and its contents, in
some state, is the R-value (right-hand value) of the identifier, in that state. The lifetime of the
location is related to, but logically distinct from the scope of the identifier. Thus we have a
two-level picture
69
ρ
σ
....... .......❍ ✟❍
.......
....
❍✟
❍✟✟ ✲
x ... ❍❍✟✟❍❍✟✟ ✲
l.
v
✻
✻
identifier
✻
✻
location
environment
✻
value
store
The L/R value terminology comes from considering assignment statements
x := y
where on the left we think of the variable as referring to a location and on the right as referring
to a value. Indeed we analyse the effect of assignment as changing the contents of the location
to the R-value of y:
ρ
x := y, σ −→ σ[ρx = σ(ρy)]
This is of course a more complicated analysis of assignment than in Chapter 2. The L/R terminology is a little inappropriate in that some programming languages write their assignments
in the opposite order and also in that not all occurrences on the left of an assignment are
references to L-values.
The general idea of locations and separation of environments and stores comes from the ScottStrachey tradition (e.g., [Gor,Ten,Led]); it is also reminiscent of ideas of individuals in modal
logic [Hug]. In fact we do not need locations for most of the problems we encounter in the rest
of this chapter (see exercise 26) but they will provide a secure foundation for later concepts
such as
•
•
•
•
Static binding of the same global variables in different procedure bodies (storage sharing).
Call-by-reference (aliasing problems).
Arrays (location expressions).
Reference types (anonymous references).
On the other hand it would be interesting to see how far one can get without locations and to
what extent programming languages would suffer from their excision (see [Don][Rey]). One can
argue that it is the concept of location that distinguishes imperative from applicative languages.
We now make all this precise by considering a suitable mini-language.
Syntax:
• Basic Sets:
Types:
Numbers:
Truth-values:
τ ∈ T ypes = {bool, nat}
m, n ∈ N
t∈T
70
Binary Operations: bop ∈ Bop
• Derived Sets
Constants:
con ∈ Con where con ::= m | t
Expressions:
e ∈ Exp where
e ::= con | x | ∼e | e0 bop e1 | if e0 then e1 else e2
Declarations:
d ∈ Dec where
d ::= nil | const x : τ = e | var x : τ = e | d0 ; d1 |
d0 and d1 | d0 in d1
Commands:
c ∈ Com where
c ::= nil | x := e | c0 ; c1 | if e then c0 else c1 |
while e do c | d; c
Note: On occasion we write begin c end for (c). That is begin . . . end act as command
parentheses, and have no particular semantic significance. However, their use can make scopes
more apparent.
The whole of our discussion of defining, applied, and free and bound occurrences carries over
to commands and is illustrated by the command in figure 2.
var
x : bool = tt ;
✻
✻
var
❃
✚
✚
y : int = if x then 0 else z;
✻
❃
✚
✚
z : bool = if ∼ (x =0) then tt else v;
const
✻
begin
y : = if x then 0 else z
x : = tt or v
❃
✚
✚
end
Bindings
Note that left-hand variable occurrences in assignments are applied, not binding.
71
Static Semantics
Identifiers: For expressions we need the set, FI(e), of identifiers occurring freely in e (defined
as usual). For declarations we need the sets FI(d) and DI(d) of identifiers with free and defining
occurrences in d; they are defined just like in the case of definitions and of course
FI(const x : τ = e) = FI(var x : τ = e) = FI(e)
DI(const x : τ = e) = DI(var x : τ = e) = {x}
For commands we only need FI(c) defined as usual plus FI(d; c) = FI(c)\DI(d).
Type-Checking: We take
TEnv = Id −→fin (Types + Types × {loc})
and write α : I for any α in TEnv with domain I ⊆ Id. The idea is that α(x) = τ means that x
def
denotes a value of type τ , whereas α(x) = τ loc ( = τ, loc ) means that x denotes a location
which holds a value of type τ .
Assertions:
• Expressions: For each I and expression e with FI(e) ⊆ I and type-environment α : I we
define
α
I
e:τ
meaning that given α the expression e is well-formed and of type τ .
• Declarations: Here for each I and declaration d with FI(d) ⊆ I and type-environment α : I
we define
α
I
d:β
meaning that given α the declaration d is well-formed and yields the type-environment β.
• Commands: Here for each I and command c with FI(c) ⊆ I and type-environment α : I we
define:
α
I
c
meaning that given α the command c is well-formed.
Rules:
• Expressions: As usual except for identifiers where:
Identifiers: α I x : τ
(if α(x) = τ or α(x) = τ loc)
• Declarations: Just like definitions before, except for simple ones:
72
α I e:τ
α I const x : τ = e : {x = τ }
α I e:τ
Variables:
α I var x : τ = e : {x = τ loc}
• Commands: The rules are similar to those in Chapter 2. We give an illustrative sample.
Nil:
α I nil
α I e:τ
Assignment:
(if α(x) = τ loc)
α I x := e
α I c0
α I c1
Sequencing:
α I c0 ; c1
α I d : β α[β] I∪I0 c
Blocks:
(where β : I0 )
α I d; c
Constants:
Dynamic Semantics
Following the ideas on environments and stores we consider suitably typed locations and assume
we have for each τ infinite sets
Locτ
which are disjoint and that (in order to create new locations) we have for each I ⊆ Locτ a
location Newτ (I) ∈ Locτ with Newτ (I) ∈ I (the new property).
Note: It is very easy to arrange these matters. Just put Locτ = N × {τ } and Newτ (I) =
µm. m, τ ∈ I, τ .
Now putting Loc =
Locτ we take for
τ
Stores = {σ : L ⊆ Loc −→fin Con | ∀l ∈ Locnat ∩ L. σ(l) ∈ N
∧∀l ∈ Locbool ∩ L. σ(l) ∈ T}
(as Con is the set of values). And we also take
Env = Id −→fin Con + Loc
For any ρ : I and α : I we define ρ : α by:
ρ : α ≡ ∀x ∈ I. (α(x) = bool ∧ ρ(x) ∈ T) ∨ (α(x) = nat ∧ ρ(x) ∈ N)
∨ ∃τ. (α(x) = τ loc ∧ ρ(x) ∈ Locτ )
Transition Relations:
73
• Expressions: For any α : I we set
Γα = { e, σ | ∃τ. α
Tα = { con, σ }
I
e : τ}
and for any α : I we will define transition relations of the form
ρ
α
e, σ −→ e , σ
where ρ : α and e, σ and e , σ are in Γα .
• Declarations: We extend Dec by adding the production
d ::= ρ
and putting FI(ρ) = ∅ and DI(ρ) = I (where ρ : I), and putting α
Now for any α : I we take
Γα = { d, σ | ∃β. α
I
d : β} ∪ {ρ}
and
I
ρ : β (where ρ : β).
Tα = {ρ}
and the transition relation has the form
ρ
α
d, σ −→ d , σ (or ρ )
where ρ : α and d, σ and d , σ (or ρ ) are in Γα .
• Commands: For any α : I we take
Γα = { c, σ | α
I
c} ∪ {σ}
Tα = {σ}
and
and the transition relation has the form
ρ
α
c, σ −→ c , σ
(or σ )
where ρ : α and c, σ and c , σ (or σ ) are in Γα .
Rules:
• Expressions: These should be fairly obvious and we just give some examples.
Identifiers:
(1) ρ α x, σ −→ con, σ
(if ρ(x) = con)
(2) ρ α x, σ −→ con, σ
(if ρ(x) = l and σ(l) = con)
ρ α e0 , σ −→ e0 , σ
Conditional:
(1)
ρ α if e0 then e1 else e2 , σ −→ if e0 then e1 else e2 , σ
• Declarations:
Nil:
(2) ρ
α
if tt then e1 else e2 , σ −→ e1 , σ
(3) ρ
α
if ff then e1 else e2 , σ −→ e2 , σ
ρ
α
nil, σ −→ ∅, σ
74
Constants:
Variables:
(1)
α
(2) ρ α const x : τ = con, σ −→ {x = con}, σ
Informally to elaborate var x : τ = e from state σ given ρ
(1) Evaluate e from state σ given ρ yielding con.
(2) Get a new location l and change σ to σ[l = con] and yield {x = l}.
Formally
ρ α e, σ −→ e , σ
(1)
ρ α var x : τ = e, σ −→ var x : τ = e , σ
(2) ρ
Sequential:
ρ
ρ α e, σ −→ e , σ
const x : τ = e, σ −→ const x : τ = e , σ
α
var x : τ = con, σ −→ {x = l}, σ[l = con]
(1)
(where σ : L and l = Newτ (L ∩ Locτ ))
ρ α d0 , σ −→ d0 , σ
ρ α d0 ; d1 , σ −→ d0 ; d1 , σ
(2)
ρ[ρ0 ] α[α0 ] d1 , σ −→ d1 , σ
ρ α ρ0 ; d1 , σ −→ ρ0 ; d1 , σ
(where ρ0 : α0 )
ρ α ρ0 ; ρ1 , σ −→ ρ0 [ρ1 ], σ
Like Sequential.
ρ α ρ0 in ρ1 , σ −→ ρ1 , σ
Like Sequential.
ρ α d1 , σ −→ d1 , σ
(2)
ρ α ρ0 and d1 , σ −→ ρ0 and d1 , σ
(3)
Private:
1./2.
3.
Simultaneous: (1)
(3) ρ α ρ0 and ρ1 , σ −→ ρ0 ,ρ1 , σ
Note: These definitions follow those for definitions very closely.
• Commands: On the whole the rules for commands are much like those we have already seen
in Chapter 2.
Nil:
ρ α nil, σ −→ σ
ρ α e, σ −→∗ con, σ
Assignment:
ρ α x := e, σ −→ σ [l = con]
(where ρ(x) = l, and if l ∈ L where σ : L)
Composition: 1./2. Like Chapter 2, but with ρ.
Conditional While: Like Chapter 2, but with ρ.
Blocks:
Informally to execute d; c from σ given ρ
(1) Elaborate d from σ given ρ yielding ρ0 and a store σ .
(2) Execute c from σ given ρ[ρ0 ] yielding σ . Then σ is the result of the
execution.
ρ α d, σ −→ d , σ
(1)
ρ α d; c, σ −→ d ; c, σ
(2)
ρ[ρ0 ] α[α0 ] c, σ −→ c , σ
ρ α ρ0 ; c, σ −→ ρ0 ; c , σ
75
(ρ0 : α0 )
(3)
ρ[ρ0 ] α[α0 ] c, σ −→ σ
ρ α ρ0 ; c, σ −→ σ
In the above we have not connected up ρ and σ. In principle it could happen either that
(1) There is an l in the range of ρ but not in the domain of σ. This is an example of a dangling
reference. They are also possible in relation to a configuration such as c, σ where l occurs
in c (via some ρ) but not in the domain of σ.
(2) There is an l not in the range of ρ but in the domain of σ. And similarly wrt c and σ, etc.
This is an example of an inaccessible reference.
However, we easily show that if for example we have no dangling references in ρ and σ,
or c and σ and if ρ
c, σ −→∗ c , σ then there are none either in ρ and σ or c and σ .
One says that the language has no storage insecurities. An easy way to obtain a language
which is not secure is to add the command
c ::= dispose(x)
with the dynamic semantics
ρ
α
dispose(x), σ −→ σ\l
(where l = ρ(x))
(and σ\l = σ\{ l, σ(l) }) (and obvious static semantics). One might wish to add an error
rule for attempted assignments to dangling references.
On the other hand according to out semantics we do have inaccessible references. For example
a block exit
ρ
var x : bool = tt, begin nil end, σ −→ {x = l}; nil, σ[l = tt]
−→ σ[l = tt]
Another example is provided by sequential or private definitions, e.g.,
ρ
var x : bool = tt; var x : bool = tt, σ −→ {x = l1 }; var x : bool = tt, σ[l1 = tt]
−→ {x = l1 }; {x = l2 }, σ[l1 = tt, l2 = tt]
−→ {x = l2 }, σ[l1 = tt, l2 = tt]
and again
ρ
var x : bool = tt in var y : bool = tt, σ −→∗ {x = l1 in y = l2 }, σ[l1 = tt, l2 = tt]
−→ {y = l2 }, σ[l1 = tt, l2 = tt]
It is not clear whether inaccessible references should be allowed. They can easily be avoided,
at the cost of complicating the definitions, by “pruning” them away as they are created, a kind
76
of logical garbage collection. We prefer here to leave them in, for the sake of simple definitions;
they do not, unlike dangling references, cause any harm.
The semantics for expressions is a little more complicated than necessary in that if ρ
e, σ −→
e , σ then σ = σ ; that is there are no side-effects. However, the extra generality will prove
useful. For example suppose we had a production:
e ::= begin c
result e
To evaluate begin c result e from σ given ρ one first executes c from σ given ρ yielding σ and
then evaluates e from σ given ρ. The transition rules would, of course, be:
ρ
ρ
α
ρ α c, σ −→ c , σ
begin c result e, σ −→ begin c result e, σ
α
ρ α c, σ −→ σ
begin c result e, σ −→ e, σ
(and the static semantics is obvious).
With this construct one also has now the possibility of side-effects during the elaboration of
definitions; previously we had instead that if
ρ
α
then σ
d, σ −→ d , σ
L = σ where σ : L.
We note some other important constructs. The principle of qualification suggests we include
expression blocks:
e ::= let d
in e
with evident static semantics and the rules
ρ
ρ
ρ
α
α
ρ α d, σ −→ d , σ
let d in e, σ −→ let d in e, σ
α
ρ[ρ0 ] α[α0 ] e, σ −→ e , σ
let ρ0 in e, σ −→ let ρ0 in e , σ
let ρ0 in con, σ −→ con, σ
As another kind of atomic declaration consider
d ::= x == y
77
(where ρ0 : α0 )
meaning that x should refer to the location referred to by y (in ρ). The relevant static semantics
will, of course, be:
DI(x == y) = {x}; FI(x == y) = {y}
α I x == y : {x = τ loc}
(if α(y) = τ loc)
and the dynamic semantics is:
ρ
α
x == y, σ −→ x = l, σ
(if ρ(y) = l)
This construct is an example where it is hard to do without locations; more complex versions
allowing the evaluation of expressions to references will be considered in the next chapter.
It can be important to allow initialisation commands in declarations such as
d ::= d
initial
c
end
and the static semantics is:
DI(d initial c end ) = DI(d);
FI(d initial c end) = FI(d) ∪ (FI(c)\DI(d))
and
α
d:β
α[β] I∪I0 c
α I d initial c end
I
(if β : I0 )
However, we may wish to add other conditions (like the drastic FI(c) ⊆ DI(d)) to avoid sideeffects. The dynamic semantics is:
ρ
ρ
ρ
α
ρ α d, σ −→ d , σ
d initial c end, σ −→ d initial c end, σ
α
ρ α[α0 ] c, σ −→ c , σ
ρ0 initial c end, σ −→ ρ0 initial c end, σ
α
ρ[ρ0 ] α[α0 ] c, σ −→ σ
ρ0 initial c end, σ −→ ρ0 , σ
(where ρ0 : α0 )
In the exercises we consider a dual idea of declaration finalisation commands which are executed
after the actions associated with the scope rather than before the scope of the declaration.
Finally, we stand back a little and look at the various classes of values associated with our
language.
78
• Expressible Values: These are the values of expressions. In our language this set, EVal, is
just the set, Con, of constants.
• Denotable Values: These are the values of identifiers in environments. Here the set, DVal,
is the set Con + Loc of constants and locations. Note, that Env = Id −→fin DVal.
• Storeable Values: These are the values of locations in the store. Here, the set, SVal, is the
set Con of constants. Note, that Stores is the set of type-respecting finite maps from Loc to
SVal.
Thus we can consider the sets EVal, DVal, SVal of expressible, denotable and storeable values;
languages can differ greatly in what they are and their relationship to each other [Str]. Other
classes of values – e.g., writeable ones – may also be of interest.
5.5
Exercises
1. It is possible to formalise the notion of occurrence. An occurrence is a sequence l =
m1 . . . mn (n ≥ 0) of non-zero natural numbers. For any expression, e, (say in the first
language of Chapter 3) and occurrence, l, one has the expression e = Occ(e, l) occurring
in e at l (it may not be defined). For example
Occ(e, ε) = e
Occ(let x = e0 in e1 , m
Occ(x, l)
(m = 1)
Occ(e0 , l)
(m = 2)
Occ(e1 , l)
undefined
(m = 3)
l) =
(otherwise)
Define Occ(e, l) in general. Define FO(x, e) = the set of free occurrences of x in e and also
the sets AO(x, e) and BO(x, e) of applied and binding occurrences of x in e. For any l in
BO(x, e) define Scope(l) = the set of applied occurrences of x in the scope of l; for any
bound occurrence, l, of x in e (i.e., l in [AO(x, e) ∪ BO(x, e)]\FO(x, e), define binder(l)
the unique occurrence in whose scope l is.
2. Repeat exercise 1 for the other languages in Chapter 3 (and later chapters!).
3. Ordinary mathematical language also has binding constructions. Notable are such examples as integration and summation.
x
y
f (y) dy dx
0
1
an x n
and
n≥0
Define mathematical expression language with these constructs and then define free variables and occurrences etc, just as in exercise 1.
79
4. The language of predicate logic also contains binders. Given a syntax for arithmetic
expressions (say) we can define formulae by:
F ::= e = e | e > e | . . . | ¬F | F ∨ F | F ∧ F | F ⊃ F | ∀x. F | ∃xF
where ∧, ∨, ⊃ mean logical and, or and implies and to assert ∀x. F means that for all
x we have F and to assert ∃x. F means that we have F for some x. Repeat the work
of exercise 3 for predicate logic. To what extent is it feasible to construct an operational
semantics for the languages of exercise 3 and 4? How would it help to only consider finite
sums,
e and quantifications ∀x. ≤ b.F and piecewise approximation?
a≤n≤b
5. Can you specify the location of dynamic errors? Thus starting from c, σ suppose we reach
c , σ and the next action is (for example) division by zero; then we want to specify an error
occurred as some occurrence in the original command c. [Hint: Add a labelling facility,
c ::= L :: c and transition rules for it, and start not from c but a labelled version in which
the occurrences are used for labels.]
6. Define the behaviour and equivalence of definitions and expressions of the second language
of this chapter; prove that the program constructs respect equivalence. Establish or refute
each of the following suggested equivalences
d0
d0
d0
d0
and (d1 and d2 ) ≡ (d0 and d1 ) and d2
and d1 ≡ d1 and d0
and nil ≡ d0
and nil ≡ nil
and similar ones for private and sequential definition.
7. Show that the following right-distributive law
d0 in (d1 and d2 )
≡
(d0 in d1 ) and (d0 and d2 )
holds. What about the left-distributive law? What about other such laws? Show that
d0 in (x = e) ≡ x = let d0 in e. Show that d0 ; d1 ≡ d0 in (d1 and dV ) where V =
DV(d0 )\DV(d1 ) and where for any V = {x1 , . . . , xn } we put dV = x1 = x1 and . . . and xn =
xn . Conclude that any d can be put, to within equivalence, in the form x1 = e1 and . . . and xn =
en .
8. Show that let d0 ; d1 in e ≡ let d0 in (let d1 in e). Under what general conditions do we
have d0 ; d1 ≡ d1 ; d0 ? When do we have d0 ; d1 ≡ d0 in d1 ? When do we have let d0 ; d1
in e ≡ let d0 in d1 in d0 ; e?
9. It has been said that in blocks like let d0 in e all free variables of e should be bound by
d for reasons of programming readability. Introduce strict blocks let d0 in e and d0 in d1
where it is required that FV(e) (resp. FV(d1 )) ⊆ DV(d0 ). Show that the non-strict blocks
80
are easily defined in terms of the strict ones. [Hint: Use simultaneous definitions and the
dV of exercise 7.] Investigate equivalences for the strict constructions.
10. Two expressions (of the first language of the present chapter) e and e are α-equivalent written e ≡α e - if they are identical “up to renaming of bound variables”. For example
let x = e in let y = e in x + y ≡α let y = e in let x = e in y + x
if x, y ∈ FV(e ), but let x = e in x + y ≡α let y = e in y + y. Define α-equivalence.
[Hint: For a definition by structural induction to show let x = e0 in e1 ≡α let y =
e0 in e1 it is necessary to show some relation between e1 and e1 . So define π : e ≡α e
where π : FV(e) ∼
= FV(e ) is a bijection; this relation means e is α-equivalent to e up
to the renaming, π, of the free variables.] Show that e ≡α e implies e ≡ e . Show that
for any e there is an e with e ≡α e and no bound variable of e in some specified finite
set and no variable of e has more than one binding occurrence.
11. Define for the first language of the present chapter the substitution of an expression e
for a variable x in the expression e - written [e/x]e ; in the substitution process no free
variable of e should be captured by a binding occurrence in e , so that some systematic
renaming of bound variables will be needed. For example we could not have
[x/y] let x = e in x + y = let x = [x/y] e in x + x
but could have
[x/y] let x = e in x + y = let z = [x/y] e in z + x
where z = x. Show the following
let x = e in e ≡α let y = e in [y/x]e (if y ∈ FV(e ))
[e/x][e /y]e ≡α [[e/x]e /y][e/x]e
(if x = y)
[e/x][e /x]e ≡α [[e/x]e /x]e
[e/x]e ≡α e
(if x ∈ FV(e ))
FV([e/x]e ) = FV(e) ∪ (FV(e )\{x})
[e/x]e ≡ let x = e in e .
12. By using substitution we could avoid the use of environments in the dynamic semantics
of the first language of the present chapter. The transition relation would have the form
e −→ e for closed e, e (no free variables) and the rules would be as usual for binary
operations, none (needed) for identifiers, and let x = e0 in e1 −→ [e0 /x]e1 . Show this
gives the same notion of behaviour for closed expressions as the usual semantics.
13. Extend the work of exercises 10, 11 and 12 to the second language of the present chapter.
81
14. It is possible to have iterative constructs in applicative languages. Tennent has suggested
the construct
e = for x = e0 to e1 op bop on e2
So that, for example, if e0 = 0 and e1 = n and bop = + and e2 = x∗x then e =
x∗x.
0≤x≤n
Give the operational semantics of this construct.
15. It is even possible to use definitions to obtain analogues of while loops. Consider the
definition construct
d = while e do d
So that
let private x = 1 and y = 1
within while y = n
do x = x ∗ y and y = y + 1
in x
computes n! for n ≥ 1. Give this construct a semantics; show that the construct of exercise
14 can be defined in terms of it. Is the new construct a “good idea”?
16. Consider the third language of the present chapter. Show that the type-environments
generated by definitions are determined by defining by Structural Induction a partial
function DTE: Definitions −→ TEnv and then proving that for any α, V, d, β:
α
V
d : β ⇒ DTE(d) is defined and equal to β.
17. Give a semantics to a variant of the third language in which the types of variables are
not declared and type-checking is dynamic.
18. Change the fourth language of the present chapter so that the atomic declarations have
the more usual forms:
const x = e
and
var x : τ
Can you type-check the resulting language? To what extent can you impose in the static
semantics the requirement that variables should be initialised before use? Give an operational semantics following one of the obvious alternatives regarding initialisation at
declaration:
(1) The variable is initialised to a conventional value (e.g., 0/ff), or an unlikely one (e.g.,
the maximum natural number available/?).
82
(2) The variable is not initialised at declaration. [Hint: Use undefined maps for stores
or (equivalently) introduce a special UNDEF value into the natural numbers (and
another for truth-values).] In this case show how to specify the error of access before
initialisation. Which alternative do you prefer?
19. In PL/I identifiers can be declared to be “EXTERNAL”; as such they take their value
from an external environment - and so the declaration is an applied occurrence - but
they have local scope - and so the declaration is also a binding occurrence. For example
consider the following fragment in an extension of our fourth mini-language (not PL/I!)
(where we allow d ::= external x : τ ):
external x : nat;
begin
x := 2;
var x : nat;
begin
x := 1;
external
x : nat;
begin y := x end
end
end
This sets y equal to 2. Give a semantics to external declarations.
20. In PL/I variables can be declared without storage allocation being made until explicitly
requested. Thus a program fragment like
var
x : nat
begin
x := 1; allocate(x)
end
would result in a dynamic error under that interpretation of variable declaration. Give a
semantics to this idea.
21. In the programming language EUCLID it is possible to declare identifiers as pervasive,
meaning that no holes are allowed in their scope - they cannot be redeclared within their
scope. Formulate an extension of the imperative language of this chapter which allows
83
pervasive declarations and give it a static semantics. Are there any problems with its
dynamic semantics?
22. Formalise Dijkstra’s ideas on scope as presented in Section 10 of his book, A Discipline of
Programming (Prentice-Hall, 1976). To do this define and give a semantics to a variant
of the fourth mini-language which incorporates his ideas in as elegant a way as you can
manage.
23. Suppose we have two flavours of variable declaration
local var x : τ
and
heap var x : τ
(cf PL/I, ALGOL 68). From an implementation point of view local variables are allocated
space on the stack and heap ones on the heap; from a semantical point of view the locations
are disposed of on block exit (i.e., they live until the end of the variable’s scope is reached)
or never (unless explicitly disposed of). Formalise the semantics for these ideas. Does
replacing local by heap make any difference to a program’s behaviour? If not, find some
language extensions for which it does.
24. Add to the considerations of exercise 23 the possibility
static var x : τ
Here, the locations are allocated as part of the static semantics (of FORTRAN, COBOL,
PL/I).
25. Consider the finalisation construct d = d0 final c. Informally to elaborate this from
an environment ρ one elaborates d0 obtaining ρ0 but then after the actions (whether
elaboration, execution or evaluation) involved in the scope of d one executes c in the
environment ρ = ρ[ρ0 ] (equivalently, one executes ρ ; c). Give an operational semantics for
an extension of the imperative language of the present chapter by a finalisation construct.
[Hint: The elaboration of declarations should result in an environment and a command
(with no free identifiers).] Justify your treatment of the interaction of finalisation and the
various compound definition forms.
26. How far can you go in treating the constructs of the imperative language of this chapter
(or later ones) without using locations? One idea would be for declarations to produce
couples < ρ, σ > of environments and stores (in the sense of Chapter 2) where ρ : I1 , σ :
I2 and I1 ∩ I2 = φ. What problems arise with the declaration x == y?
27. Formalise the notion of accessibility of a location and of a dangling location by defining
when given an environment ρ and a configuration c, σ (or d, σ or e, σ ) a location,
l, is accessible. Define the notion of lifetime with respect to the imperative language of
the present chapter. Would it be best to define it so that the lifetime of a location ended
exactly when it was no longer accessible or dangling? Using your definition formulate and
84
prove a theorem, for the imperative language, relating scope and lifetime.
28. Locations can be considered as “dynamic place holders” (in the execution sequence) just
as we considered identifiers as “static place holders” (in program text). Draw some arrow
diagrams for locations in execution sequences to show their creation occurrences analogous
to those drawn in this chapter to show binding occurrences.
29. Define α-equivalence for the imperative programming language of the present chapter
(see exercise 10). One can consider c ≡α c as saying that c and c are equivalent up
to choice of static place holders. Define a relation of location equivalence between couples
of environments and configurations, written ρ, γ ≡l ρ , γ (where γ is an expression,
command or declaration configuration); it should mean that the couples are equivalent
up to choice of locations (dynamic place holders). For example
{x = l1 }, {y = l2 }; x := x + y, {l1 = 3, l2 = 4}
≡l
{x = l2 }, {y = l1 }; x := x + y, {l2 = 3, l1 = 4}
holds.
30. Define the behaviour of commands, expressions and declarations and define an equivalence
relation ≡l between behaviours which should reflect equality of behaviours up to choice
of dynamic place holders. Prove, for example, that
(var x : nat = 1; var y : nat = 1)
≡l
(var y : nat = 1; var x : nat = 1)
even though the two sides do not have identical behaviours. Investigate the issues of
exercises 10, 11, and 12 using ≡l .
5.6 Remarks
The ideas of structuring definitions and declarations seem to go back to Landin [Lan] and Milne
and Strachey [Mil]. The idea of separating environments and stores, via locations, can also be
found in [Mil]. The concepts of scope, extent, environments, stores and their mathematical
formulations seem to be due to Burstall, Landin, McCarthy, Scott and Strachey. [I do not want
to risk exact credits, or exclude others . . . ] For another account of these matters see [Sto].
The ideas of Section 5.4 on static semantics where the constraints are clearly context-sensitive
in general were formulated in line with the general ideas on dynamic semantics. In fact, they
are simpler as it is only needed to establish properties of phrases rather than having relations
between them. lt is hoped that the method is easy to read and in line with one’s intuition.
There are many other methods for the purpose and for a survey with references, see [Wil].
It is also possible to use the techniques of denotational semantics for this purpose [Gor,Sto].
Our method seems particularly close to the production systems of Ledgard and the extended
85
attribute grammars used by Watt; one can view, in such formulae as α V d : β, the turnstile
symbols α and V as inherited attributes and β as a synthesized attribute of the definition
d; obviously too the type-environments α and β are nothing but symbol tables. It would be
interesting to compare the methods on a formal basis.
As pointed out in exercise 26 one can go quite far without using locations. Donahue also tries
to avoid them in [Don]. In a first version of our ideas we also avoided them, but ran into
unpleasantly complicated systems when considering shared global variables of function bodies.
As pointed out in exercise 12 one can try to avoid environments by using substitutions; it
is not clear how far one can go in this direction (which is the usual one in syntactic studies
of the λ-calculus). However, we have made a definite decision in these notes to stick to the
Scott-Strachey tradition of environments. Note that in such rules as
let x = e0 in e1 −→ [e0 /x]e1
there is no offence against the idea of syntax-directed operational semantics. It is just that
substitution is a rather “heavy” primitive and one can argue that the use of environments is
closer to the intuitions normally used for understanding programming languages. (One awful
exception is the ALGOL 60 call-by-name mechanism.)
6
Bibliography
[Ack]
[Don]
[Gor1]
[Gor2]
[Hin]
[Hug]
[Lan1]
[Lan2]
[Led]
[Mil]
[Pra]
[Rey]
Ackerman, W.B. (1982) Data Flow Languages, IEEE Computer 15(2):15–25.
Donahue, J.E. (1977) Locations Considered Unnecessary, Acta Informatica 8:221–242.
Gordon, M.J., Milner, A.J.R.G. and Wadsworth, C.P. (1979) Edinburgh LCF, LNCS
78, Springer.
Gordon, M.J. (1979) The Denotational Description of Programming Languages,
Springer.
Hindley, J.R., Lercher, B. and Seldin, J.P. (1972) Introduction to Combinatory Logic,
Cambridge University Press.
Hughes, G.E. and Cresswell, M.J. (1968) An Introduction to Modal Logic, Methuen.
Landin, P.J. (1964) The Mechanical Evaluation of Expressions, Computer Journal
6(4):308–320.
Landin, P.J. (1965) A Correspondence between ALGOL 60 and Church’s Lambdanotation, Communications of the ACM 8(2):89–101 and 8(3):158–165.
Ledgard, H.F. and Marcotty, M. (1981) The Programming Language Landscape, Science
Research Associates.
Milne, R.E. and Strachey, C. (1976) A Theory of Programming Language Semantics,
Chapman and Hall.
Prawitz, D. (1971) Ideas and Results in Proof Theory, Proc. 2nd Scandinavian Logic
Symposium, ed. J.E. Fenstad, p. 237–309, North Holland.
Reynolds, J.C. (1978) Syntactic Control of Interference, Proc. POPL’78, pp. 39–46.
86
[Str]
[Sto]
[Wil]
Strachey, C. (1973) The Varieties of Programming Language, Technical Monograph
PRG-10, Programming Research Group, Oxford University.
Stoy, J.E. (1977) Denotational Semantics: The Scott-Strachey Approach to Programming Language Theory, MIT Press.
M.H. Williams (1981) Methods for Specifying Static Semantics, Computer Languages
6(1):1–17.
87
7
Functions, Procedures and Classes
In this chapter we consider various mechanisms allowing various degrees of abbreviation and
abstraction in programming languages. The idea of abbreviating the repeated use of some
expressions by using definitions or declarations of identifiers was considered in Chapter 3; if we
apply the same choice to commands we arrive at (parameterless) procedures (= subroutines). It
is very much more useful to abstract many similar computations together, different ones being
obtained by varying the values of parameters. In this way we obtain functions from expressions
and procedures from commands.
Tennent’s Principle of Abstraction declares that the same thing can be done with any semantically meaningful category of phrases. Applying the idea to definitions of declarations we obtain
a version of the class concept, introduced by SIMULA and recently taken up in many modern
programming languages. (If we just use identifiers to stand for definitions or declarations we
obtain the simpler but still most useful idea of module.)
Calling (= invoking) abstractions with actual parameters (their arguments) for the formal
ones appearing in their definition results in appropriate computations whether evaluations,
executions or elaborations of the bodies of their definitions. We will explain this by allowing
abstraction identifiers to denote closures which record their formal parameters and bodies.
Invocations will be explained in terms of computations of blocks chosen in terms of Tennent’s
Principle of Correspondence which declares that in principle to every parameter mechanism
there corresponds an appropriate definition or declaration mechanism. For example if we define
f (x : nat) : nat = x + 1
then the elaboration results in the environment
f = λx : nat. x + 1 : nat
To invoke f in an expression, say f (5), we just evaluate the expression block
let x : nat = 5
in x + 1
Note that this block exists by virtue of Tennent’s Principle of Qualification.
Below we use these ideas to consider an applicative programming language with (possibly recursive) definitions of functions of several arguments. We then consider an imperative language
where we consider both functions and procedures and use the Principle of Correspondence
to obtain the parameter mechanisms of call-by-constant and call-by-value. Other parameter
mechanisms are easily handled using the same ideas (some explicitly in the text and others
in exercises); let us mention call-by-reference, call-by-result, call-by-value-result, call-by-name
88
and call-by-text. Next we consider higher order functions and procedures. Finally we use the
Principles of Abstraction and Correspondence to handle modules and classes; this needs no new
ideas although some of the type-checking issues are interesting.
7.1 Functions in Applicative Languages
We begin with the simplest case where it is possible to define functions of one argument (unary)
functions. Let us consider throughout extensions of the second applicative language of Chapter
3. Add the following kind of function definitions:
d ::= f (x : τ0 ) : τ1 = e
and function calls
e ::= f (e)
where f is another letter we will use to range over variables (but reserving its use to contexts
where functions are expected).
Static Semantics
This is just as before as regards free and defining variables with the extensions
FV(f (x : τ0 ) : τ1 = e) = FV(e)\{x}
DV(f (x : τ0 ) : τ1 = e) = {f }
FV(f (e)) = {f } ∪ FV(e)
It is convenient to consider types a little more systematically than before. Just as we have
expressible and denotable values (EVal and DVal) we now introduce the sets of ETypes and
DTypes, expressible and denotable types (ranged over by et and dt respectively) where
et ::= τ
dt ::= τ | τ0 → τ1
More complex expressible types will be needed later; denotable types of the form τ0 → τ1 will
be used for functions which take arguments of type τ0 and deliver results of type τ1 . Later we
will want also sets of storeable types and other such sets. Now we take
TEnv = Var −→fin DTypes
ranged over, as before, by α and β and give rules for the predicates
α
V
e : et
89
where α : V and FV(e) ⊆ V , and
α
V
d:β
where α : V and FV(d) ⊆ V . These rules are just as before with the evident extensions for
function calls and definitions:
Function Calls:
Function Definitions:
α
e : et0
(if α(f ) = et0 → et1 )
f (e) : et1
α V e : τ1
f (x : τ0 ) : τ1 = e : {τ0 → τ1 }
V
α
V
α
V
Dynamic Semantics
We introduce the set, Closures, of closures
Closures = {λx : et0 . e : et1 | {x = et0 }
{x}
e : et1 }
and define the set of denotable values by
DVal = Con + Closures
and then we define, as usual,
Env = Var −→fin DVal
and add the following production to the definition of the category of definitions
d ::= ρ
(and put for ρ : V , DV(ρ) = V and FV(ρ) = ∅).
It is important to note that what is meant here is that the sets Dec, Exp, Closures, DVal and
Env are being defined mutually recursively. For example the following is an expression of type
nat
let f = λx : nat
(let{y = 3, g = λy : bool. ∼y : bool} in if g(ff) then x else y) : nat
and w = 5
in f (2) + w
There is no more harm in such recursions than in those found in context-free grammars; a
detailed discussion is left to Appendix B.
90
Note too that closures have in an obvious sense no free variables. This raises the puzzle of
what we intend to do about the free variable in function definitions. In fact in elaborating such
definitions we will bind the free variables to their values in the elaboration environment. This
is known as static binding (= binding of free variables determined by their textual occurrence),
and will be followed throughout these notes. The alternative of delaying binding until the
function is called, and then using the calling environment, is known as dynamic binding, and is
considered in the exercises.
To extend the static semantics we type denotable values defining the predicate for dval in DVal
and dt in DTypes
dval : dt
and for ρ : V in Env and α : V in TEnv define
ρ:α
by the rules
Constants:
Closures:
m : nat
t : bool
(λx : et0 . e : et1 ) : et0 → et1
∀x ∈ V. ρ(x) : α(x)
(where ρ : V , α : V )
Environments:
ρ:α
and add the rule for environments considered as definitions
Environments:
ρ:α
β V ρ:α
With all this we now easily extend the old dynamic semantics with the usual transition relations
ρ
ρ
α
α
e −→ e
d −→ d
by rules for function calls and definition.
• Function Calls:
ρ
α
f (e0 ) −→ let x : et0 = e0 in e
(if ρ(f ) = λx : et0 . e : et1 )
This rule is just a formal version of the Principle of Correspondence for the language under
consideration.
• Function Definitions:
ρ
α
f (x : τ0 ) : τ1 = e −→ {f = λx : τ0 . (let ρ V in e) : τ1 }
91
(where V = FV(e)\{x})
Example 25 We write f (x : τ0 ) : τ1 = e for the less readable f = λx : τ0 . e : τ1 (and miss out
τ0 and/or τ1 when they are obvious). Consider the expression
def
e = let double(x : nat) : nat = 2 ∗ x
in double(double(2))
We have
∅
∅
e −→ let ρ in double(double(2))
def
where ρ = {double(x) = 2 ∗ x} and now note the computation
double(double(2)) −→ let x : nat = double(2) in double(2)
ρ
−→ let x : nat = (let x : nat = 2 in 2 ∗ x) in 2 ∗ x
−→3 let x : nat = 4 in 2 ∗ x
−→3 8
and so
e −→∗ 8
∅
Our function calls are call-by-value in the sense that the argument is evaluated before the body
of the function. On the other hand it is evaluated just after the function call; a slight variant
effects the evaluation before.
• Function Call (Amended)
ρ V e −→ e
(1)
ρ V f (e) −→ f (e )
(2) ρ
V
f (con) −→ let x : τ0 = con in e
(if f (x : τ0 ) = e is in ρ)
This variant has no effect on the result of our computations (prove this!) although it is not
hard to define imperative languages where there could be a difference (because of side-effects).
Another important possibility – call-by-name – is considered below and in the exercises.
We now consider how to extend the above to definitions of functions of several arguments such
as
max(x : nat, y : nat) : nat = if x ≥ y then x else y
Intending to use the Principle of Correspondence to account for function calls we expect such
92
transitions as
ρ
max(3, 5) −→
let x : nat, y : nat = 3, 5
in if x ≥ y then x else y
and therefore simultaneous simple definitions. To this end we adopt a “minimalist” approach
adding two syntactic classes to the applicative language of the last chapter.
Formals: This is the set Forms ranged over by form and given by
form ::= · | x : τ, form
Actual Expressions: This is the set AcExp ranged over by ae where
ae ::= · | e, ae
Then we extend the category of definitions allowing more simple definitions and function definitions
d ::= form = ae | f (form) : τ = e
and adding function calls to the stock of expressions
e ::= f (ae)
To obtain a conventional notation x : τ , · and e, · are written x : τ and e respectively and f ()
replaces f (·). In a “maximalist” solution we could include actual expressions as expressions and
allow corresponding “tuple” types as types of identifiers and function results; see exercise 2.
Static Semantics
Formals give rise to defining variable occurrences
DV(·) = ∅
DV(x : τ, form) = {x} ∪ DV(form)
Then we have free variables in actual expressions
FV(·) = ∅
FV(e, ad) = FV(e) ∪ FV(ae)
and for the new kinds of definitions
FV(form = ae) = FV(ae)
DV(form = ae) = DV(form)
FV(f (form) : τ = e) = FV(e)\DV(form)
DV(f (form) : τ = e) = {f }
93
and for function calls, FV(f (ae)) = {f } ∪ FV(ae).
Turning to types we now have ETypes, AcETypes (ranged over by aet) and DTypes where
aet ::= · | τ, aet
et ::= τ
dt ::= et | aet → et
Then with TEnv = Var −→fin DTypes as always we have the evident predicates
α
V
e : et
α
V
ae : aet
α
V
d:β
Formals give positional information and type environments. So we define T : Formals −→
AcETypes by
T (·) = ·
T (x : τ, form) = τ, T (form)
and give rules for the predicate form : β
(1) · : ∅
(2) form : β
⇒
(x : τ, form) : {x = τ }, β (if x ∈ DV(form))
Note that it is here the natural restriction of no variable occurring twice in a formal is made.
Here are the rules for the other predicates:
α
α
Function Calls:
V
V
ae : aet
f (ae) : et
(if α(f ) = aet → et)
form : β α V ae : aet
α V (form = ae) : β
Definitions:
α
V
(where aet = T (form))
form : β α[β] V ∪V0 e
(f (form) : τ = e) : {f = aet −→ τ }
(where β : V0 and aet = T (form))
Actual Expr.:
α
V · : ·
α V e : et α V ae : aet
α V e, ae : et, aet
Dynamic Semantics
We proceed much as before as regards closures, denotable values and environments
Closures = {λform. e : et | ∃β, V. form : β and β : V and β
DVal = Con + Closures
Env = Var −→fin DVal
d ::= ρ
94
V
e : et}
with the free and defining variables of ρ as usual and extend the static semantics by defining
the predicates dval : dt and ρ : α much as before.
As regards transition rules we will naturally define ρ V e −→ e and ρ V d −→ d and, for
actuals, ρ V ae −→ ae . The terminal actual configurations are the “actual constants”-tuples
of constants given by the rules
acon ::= · | con, acon
As for formals they give rise to environments in the content of a value for the corresponding
actuals and so we begin with rules for the predicate
acon
(1) ·
(2)
form : ρ
·:∅
con, acon
acon form : ρ
(x : τ, form) : ρ ∪ {x = con}
While this is formally adequate enough it does seem odd to use values rather than environments
as dynamic contexts.
The other rules should now be easy to understand.
f (ae) −→ let form = ae in e
(if ρ(f ) = λform. e : et)
ρ α ae −→ ae
Definitions Simple:
ρ form = ae −→ form = ae
acon form : ρ0
ρ form = acon −→ ρ0
Function:
ρ α f (form) : τ = e −→ {f = λform. let ρ V in e : τ }
Function Calls:
Actual Expr.:
ρ
ρ
ρ
α
α
α
(where V = FV(e)\DV(form))
e −→ e ⇒ ρ α e, ae −→ e , ae
ae −→ ae ⇒ ρ α con, ae −→ con, ae
Example 26 We calculate the maximum of 2 + 3 and 2 ∗ 3. Let ρ0 be the environment {max =
λx : nat, y : nat. let ∅ in if x ≥ y then x else y : nat}. Then we have
∅
{let max(x : nat, y : nat) : nat = if x ≥ y then x else y} in max(2 + 3, 2 ∗ 3)
−→ let ρ0 in max(2 + 3, 2 ∗ 3)
−→ let ρ0 in let x : nat, y : nat = 2 + 3, 2 ∗ 3 in let ∅ in (if x ≥ y then x else y)
−→∗ let ρ0 in let {x = 5, y = 6} in let ∅ in (if x ≥ y then x else y)
−→∗ let ρ0 in let {x = 5, y = 6} in let ∅ in 6
−→3 6.
as one sees that
ρ0
x : nat, y : nat = 2 + 3, 2 ∗ 3 −→∗ {x = 5, y = 6}
95
Recursion
It will not have escaped the readers attention that no matter how interesting our applicative
language may be it is useless as there is no ability to prescribe interesting computations. For
example we do not succeed in defining the factorial function by
def
d = fact(n : nat) : nat = if n = 0 then 1 else n ∗ fact(n − 1)
as the fact on the right will be taken from the environment of dfact and not understood recursively. (Of course the imperative languages are interesting owing to the possibility of loops;
note too exercise 3, 14, 15.)
Clearly we need to introduce recursion. Syntactically we just postulate a unary operator on
definitions (and later on declarations)
d ::= rec d
Thus rec dfact will define the factorial function. In terms of imports and exports rec d imports
all imports of d other than exports which provide the rest of the imports to d; the exports of
rec d are those of d. In other words define X to be FV(d)\DV(d), Y to be DV(d) and R to be
FV(d) ∩ DV(d). Then X is the set of imports of rec d and Y is the set of its exports with R
being defined recursively. Diagrammatically we have
✛
d
X
✲
✲ R
✲ Y
A Recursive Declaration: rec d
The unary recursion operator gives a very flexible way to make recursive definitions since the
d in rec d can take many forms other than simple function definitions like f (x : τ1 . . .) : τ = e.
Simultaneous recursive definitions are written
rec f (. . .) = . . . f . . . g . . . and . . . and
g(. . .) = . . . f . . . g . . .
A narrow scope form of sequential recursive definitions is
rec f (. . .) = . . . f . . . g . . . ; . . . ;
rec g(. . .) = . . . f . . . g . . . ;
where the g in the definition of f is taken from the environment but the f in the definition of
96
g is the recursively defined one. A wide scope form is obtained by writing
rec f (. . .) = . . . f . . . g . . . ; . . . ;
g(. . .) = . . . f . . . g . . .
which is equivalent to the simultaneous definition unless f = g for example.
Static Semantics
For free and defining variables we note that
FV(rec d) = FV(d)\DV(d)
DV(rec d) = DV(d)
We keep TEnv and DTypes, ETypes and AcETypes as before. The natural rule for recursive
declarations is
α[β R] V ∪R d : β
α V rec d : β
(where R = FV(d) ∩ DV(d))
However, this is not easy to use in a top-down fashion as given rec d and α one would have
to guess β. But, as covered by exercise 11, it would work. It is more convenient to use the fact
that in α V d : β the elaborated β does not depend on α but is uniquely determined by d, the
α only being used to check the validity of β. We make this explicit by defining two predicates
for definitions. First for any V and d with FV(d) ⊆ V and β we define
V
d:β
and secondly for any α : V and d with FV(d) ⊆ V we define
α
V
d
The first predicate can be read as saying that if d is a valid definition then it will have type β;
the second says that given α then d is valid. The other predicates will be as before
α
V
e : et
α
V
ae : aet
form : β
Rules:
• Definitions:
Nil:
Simple:
(1) V nil : ∅
(2) α V nil
form : β
(1)
V form = ae : β
97
(2)
Function:
(1)
(2)
Sequential:
(1)
(2)
Simultaneous: (1)
(2)
Private:
(1)
(2)
Recursion:
form : β α V ae : T (form)
α V form = ae
f
(form)
: τ = e : T (form −→ τ )
V
form : β α[β] V ∪V0 e : τ
α V f (form) : τ = e
V d0 : β0
V d1 : β1
V d0 ; d1 : β0 [β1 ]
α
V
d0
α
V
d0
(where β : V0 )
d0 : β α[β] V ∪V0 d1
(where β : V0 )
α V d0 ; d1
V d0 : β0
V d1 : β1
V d0 and d1 : β0 , β1
α V d0 α V d1
(if DV(d0 ) ∩ DV(d1 ) = ∅)
α V d0 and d1
V d1 : β1
V d0 in d1 : β1
V
d0 : β0 α[β0 ]
α V d0 in d1
V
V ∪V0
d1
(where β0 : V0 )
d:β
rec d : β
V
(1)
V
d : β α[β R] V ∪R d
(where R = FV(d) ∩ DV(d))
α V rec d
The other rules are as before except for expression blocks:
(2)
V
d:β
α
α
d α[β]
let d in e
V
V
V
V ∪V0
e
(where β : V0 )
Example 27 Consider the definition
d = rec f (x : nat) : nat = g(x) and g(x : nat) : nat = f (x)
Here as
∅
∅
f (x : nat) : nat = g(x) : {f = nat → nat}, etc. we have
d : {f = nat → nat, g = nat −→ nat}.
Then to see that ∅ ∅ d one just shows that {f = nat → nat, g = nat → nat} f,g d0 (where
rec d0 = d). This example also shows why it is needed to explicitly mention the result (= output)
of functions.
Dynamic Semantics
Before discussing our specific proposal we should admit that this seems, owing to a certain
clumsiness and its somewhat unnatural approach, to be a possible weak point in our treatment
of operational semantics.
98
At first sight one wants to get something of the following effect with recursive definitions
ρ[ρ0 V0 ] α∪α0 d −→∗ ρ0
ρ α rec d −→∗ ρ0
(where ρ0 : DV(d) and for suitable α0 : V0 )
Taken literally this is not possible. For example put d = f (x : nat) : nat = f (x) and suppose
ρ0 (f ) = d. Then for V = ∅ and ρ = ∅ we would have
ρ0
{f }
f (x : nat) : nat = f (x) −→ {f = λx : nat. (let ρ0 in f (x)) : nat}
and so we would have d = λx : nat. (let ρ0 in f (x)) : nat which is clearly impossible as d
cannot occur in itself (via ρ0 ). Of course it is just in finding solutions to suitable analogues of
this equation that the Scott-Strachey approach finds one of its major achievements.
Let us try to overcome the problem by not trying to guess ρ0 but trying to elaborate d without
any knowledge of the values of the recursively defined identifiers. Thus in our example we first
elaborate the body
∅
∅
f (x : nat) : nat = f (x) −→ {f = λx : nat. (let ∅ in f (x)) : nat}
and let ρ0 be the resulting “environment”. Note that we no longer have closures as there can
be free variables in the abstractions. So we know that for any imported value of f that ρ0 gives
the corresponding export. But in rec d the imports and the exports must be the same, that is
def
f = ρ(f ) in some recursive sense and we can take f = rec ρ0 . To get a closure we now take
the all important step of binding f to rec ρ0 in ρ0 and take the elaboration of rec d to be
ρ1 = {f (x : nat) : nat = let rec ρ0 in (let ∅ in f (x) : nat)}
What we have done is unwound the recursive definition by one step and bound into the body
instructions for further unwinding. Indeed it will be the case that
rec ρ0 −→ ρ1
and so when we call f (e) we will arrive at the expression
let x : nat = e in let rec ρ0 in let ∅ in f (x) : nat
Then we will evaluate the argument e, then we will unwind the definition once more (in preparation for the next call!), then we will evaluate the body. This is perhaps not too bad; in the
usual operational semantics of recursive definitions (see exercise 7) one first evaluates the argument, then unwinds the definition for the present call and then evaluates the body. Thus we
have simply performed in advance one step of the needed unwindings during the elaboration.
Let us now turn our attention to the formal details, the changes from previously mostly concern
allowing free variables in closures, and we define
Abstracts = {λform. e : et}
99
and put
DVal = Con + Abstracts
and
Env = Var −→fin DVal
and add
d ::= ρ
To extend the static semantics we define FV(dval) by
FV(con) = ∅
FV(λform. e : et) = FV(e)\DV(form)
and then for ρ : V
DV(ρ) = V
and
FV(ρ) =
FV(ρ(x))
x∈V
Now we define predicates
V
dval : dt and α
V
dval by
Constants: (1) V m : nat
(2) α V m
(3) V t : bool
(4) α V t
Abstracts: (1) V λform.e : et : T (form) → et
form : β α[β] V ∪V0 e
(where β : V0 )
(2)
α V λform. e : et
Then the rules for environments ρ : V
∀x ∈ V.
ρ(x) : β(x)
W ρ : β
∀x ∈ V. α W ρ(x)
α W ρ
(1)
(2)
W
Turning to the transition relations we define for α : V and β : W , with W ⊆ V and ρ : α W ,
and e, e in Γα (as before)
ρ
α
e −→ e
and keep the same set Γα of terminal expressions. Similarly we define ρ
ρ α d −→ d .
100
α
ae −→ ae and
The rules are formally the same as before except that for ρ : W conditions of the form ρ(f ) = . . .
are understood to mean that f ∈ W and ρ(f ) = . . . and similarly for ρ(x) = . . . (this affects
looking up the values of variables and function calls).
We need rules for recursion:
ρ X α[α0 ] d −→ d
ρ α rec d −→ rec d
(1)
(where X = FV(d)\DV(d) and taking β from the requirement that
d : β we have
α0 = β R where R = FV(d) ∩ DV(d))
(2) ρ α rec ρ0 −→ {x = con | x = con in ρ0 } ∪
{f (form) : τ = let rec ρ0 \DV(form) in e | f (form) : τ = e in ρ0 }
In other words we first elaborate d without knowing anything about the values of recursively
defined variables and then from the resulting ρ0 we yield ρ0 altered to bind its free variables by
rec ρ0 . Here are a couple of examples. More can be found in the exercises.
Example 28 Consider the traditional definition of factorial
d = rec fact(x : nat) : nat = if x = 0 then 1 else x ∗ fact(x − 1)
Then for any suitable ρ and α we have
ρ
α[α0 ]
fact(x : nat) : nat = . . . −→ ρ0
(with α0 as given above)
where ρ0 = {fact(x : nat) : nat = let ∅ in . . .} (and from now on we omit the tedious
“let ∅ in”). Then we have
ρ
α
rec d −→ rec ρ0 −→ ρ1
where ρ1 = {fact(x) = let rec ρ0 in . . .}
To compute fact(0) we look at the derivation
∅
∅
let rec d in fact(0) −→∗ let ρ1 in fact(0)
−→ let x : nat = 0 in let rec ρ0 in . . .
−→∗ let {x = 0} in let ρ1 in if x = 0 then 1 else . . .
−→3 1
Equally for fact(1) we have
∅
∅
let d in fact(1) −→∗ let {x = 1} in let ρ1 in
if x = 0 then 1 else x ∗ fact(x − 1)
−→ let {x = 1} in let ρ1 in x ∗ fact(x − 1)
−→∗ let {x = 1} in let ρ1 in 1 ∗ [let x : nat = x − 1 in rec ρ0 in . . .]
∗
101
−→∗ let {x = 1} in let ρ1 in 1 ∗ [let x = 0 in let ρ1 in . . .]
−→ 1
Example 29 It is allowed to define natural numbers or truth-values recursively. For example
consider d = (rec x = x + 1). To elaborate d given ρ = {x = 1} we must elaborate x = x + 1
from ρ\{x} = ∅ and that elaboration sticks as we must evaluate x+1 in the empty environment.
It could be helpful to specify a dynamic error in this case. Again the elaboration of
d = rec x = fact(0) and fact(x : nat) : nat = . . .
does not succeed as, intuitively, we need to know the value of fact before the elaboration –
which produces this value – has finished. On the other hand simple things like the elaboration
of rec x = 5 do succeed. If desired we could have specified in the static semantics that only
recursive function definitions were allowed.
7.2
Procedures and Functions
We now consider abstractions in imperative languages. Abstracts of expressions give rise to
functions, as before, but now with the possibility of side-effects as in:
function f (var x : nat) : nat =
begin
y := y + 1
result x + y
In several programming languages the bodies of functions are commands, but are treated, via
special syntactic devices, as expressions – see exercise 12. We take a straightforward view where
the bodies are (clearly) expressions. Abstracts of commands give rise to procedures as in:
procedure p(var x : nat)
begin
y := x + y
end
which may also have side-effects and indeed are often executed for their side-effects. To see why
we write var in the formal parameter let us see how the Principle of Correspondence allows us
to treat a procedure call. First the above declaration, d, will be elaborated thus
ρ
α
d, σ −→ {p(var x : nat) = {y = l}; y := x + y}, σ
where l = ρ(y). Then the procedure call p(e) in the resulting environment ρ will look like this
ρ
α
p(e), σ −→ var x : nat = e; begin {y = l}; y := x + y end, σ
102
And we see the reason for writing var . . . is to get an easy correspondence with our previous
declaration mechanism. The computation now proceeds by evaluating e, finding a new location
l , making l refer to the value of e in the state and then executing the body of the procedure
with x bound to l . This is very clearly nothing else but the classical call-by-value. Constant
declarations will give rise to a call-by-constant parameter mechanism.
We begin by working these ideas out in the evident extension of the imperative language of
Chapter 3. Then we proceed to other parameter mechanisms by considering the corresponding
declaration mechanisms. (Many real languages will not possess such a convenient correspondence; one way to deal with their parameter mechanisms would be to add the corresponding
declaration mechanisms when defining the set of possible configurations.)
For the extension we drop the const x : τ = e and var x : τ = e productions and add:
Expressions: e ::= let d in e | f (ae) | begin c result e
Actual Expr.: ae ::= · | e, ae
Declarations: d ::= form = ae | function f (form) : τ = e | procedure p(form) c |
rec d
Formals:
form ::= · | const x : τ, form | var x : τ, form
Commands: c ::= p(ae)
Static Semantics
We have the following sets of identifiers with the evident definitions and meanings: FI(e), FI(ae),
FI(d), DI(d), DI(form), FI(c). For example
FI(procedure p(form) c) = FI(c)\DI(form)
DI(procedure p(form) c) = {p}
FI(p(ae)) = {p} ∪ FI(ae)
Turning to types we define ETypes, AcETypes and DTypes; these are as before except that
both locations and procedures are denotable, causing a change in DTypes
et ::= τ
aet ::= · | τ, aet
dt ::= et | et loc | aet −→ et | aet proc
and of course TEnv = Id −→fin DTypes. We also need T (form) ∈ AcETypes with the evident
definition
· const x : τ, form var x : τ, form
T ·
τ, aet
τ, aet
103
Then we define the expected predicates
α
I
e : et
α
I
ae : aet
I
d:β
α
I
d form : β
α
I
c
We give some representative rules:
Procedure
Declarations:
(1)
I
procedure p(form) c : {p = T (form) proc}
form : β α[β] I∪I0 c
(where β : I0 )
α Ic
Formals:
(1) · : ∅
form : β
(2)
(if x ∈ I0 where β : I0 )
const x : τ, form : {x = τ }, β
form : β
(3)
(if x ∈ I0 where β : I0 )
var x : τ, form : {x = τ loc}, β
α I ae : aet
Procedure Calls: (1)
(if α(p) = aet proc)
α I p(ae)
(2)
Dynamic Semantics
We begin with environments, abstracts and denotable values. First the set, Abstracts (ranged
over by abs), is
Abstracts = {λform. e : et} ∪ {λform. c}
then
DVal = Con + Loc + Abstracts
where Loc is the set Locnat ∪ Locbool of Chapter 3 and
Env = Id −→fin DVal
and we add the production
d ::= ρ
and all the above is to be interpreted recursively as usual.
Then FI(dval) is defined in the obvious way; for example
FI(λform. c) = FI(c)\DI(form)
Then DI(ρ) and FI(ρ) are defined. Next we define the evident predicates
I
dval : dt
α
I
dval
I
ρ:β
α
104
I
ρ:β
as expected; for example
Procedure (1)
Abstracts:
(2)
I
λform. c : T (form) proc
form : β
α
I
α[β] I∪I0 c
λform. c
(where β : I0 )
Transition Relations: Turning to the transition relations we first need the set of stores
Stores = {σ : L ∈ Loc −→fin Con | σ respects types}
– the same as in Chapter 3.
• Expressions: We have
Γα = { e, σ | ∃et. α
I
e : et}
(for α : I)
and
Tα = { con, σ }
and the evident relation
ρ
α
e, σ −→ e , σ
• Actual Expressions: We have
Γα = { ae, σ | ∃aet. α
I
ae : aet}
(for α : I)
and
Tα = { acon, σ }
where acon is in AeCon, as before. And we have the relation
ρ
α
ae, σ −→ ae , σ
• Declarations: We have
Γα = { d, σ | α
I
d}
(for α : I)
and
Tα = { ρ, σ | ρ, σ ∈ Γα }
and the relation
ρ
α
d, σ −→ d , σ
105
• Formals: We define
acon, L
form : ρ, σ
meaning that in the context of an actual expression constant acon and given an existing set,
L, of locations the formal (part of a declaration) form yields a new (little) environment ρ
and store σ.
• Commands: We have
Γα , Tα
and
ρ
α
c, σ −→ c , σ
(or σ )
as usual.
Rules: The rules are generally just those we already know and only the new points are covered.
• Declarations:
Simple:
(1)
(2)
Procedure:
Recursive:
ρ
α
ρ α ae, σ −→ ae , σ
form = ae, σ −→ form = ae , σ
acon, L form : ρ0 , σ0
(where σ : L)
ρ α form = ae, σ −→ ρ0 , σ ∪ σ0
ρ α procedure p(form) c, σ −→ {p = λform. ρ\I; c}, σ
(where I = FI(c)\DI(form))
ρ\R α[α0 ] d −→ d
ρ α rec d −→ rec d
(1)
(where if
(2) ρ
α
FI(d)
d : β then R = FI(d) ∩ DI(d) and α = β
rec ρ0 −→ ρ1
(where ρ1 = {x = con | x = con in ρ0 } ∪
{x = l loc | x = l loc in ρ0 } ∪
{f (form) : et = let rec ρ0 \I in e |
f (form) : et = e in ρ0 and I = DI(form)} ∪
{p(form). rec ρ0 \I; c |
p(form) c in ρ0 and I = DI(form)})
• Formals:
Empty:
·, L
• Declarations
Empty:
· : ∅, ∅
acon, L form : ρ0 , σ0
(con, acon), L const x : τ, form : ρ0 ∪ {x = con}, σ0
106
R)
Variable:
(con, acon), L
acon, L ∪ {l} form : ρ0 , σ0
var x : τ, form : {x = l} ∪ ρ0 , {l = con} ∪ σ0
(where l = Newτ (L ∩ Locτ ))
Example 30 The following program demonstrates the use of private variables shared between
several procedures. This provides a nice version of ALGOL’s own variables and anticipates the
facilities provided by classes and abstract data types. Consider the command
c = private var x : nat = 1
within procedure inc() x := x + 1
procedure dec() if x > 0 then x := x − 1 else nil;
begin
inc(); dec()
end
First look at the declaration part, d:
ρ
d, σ −→ private {x = l} within procedure inc()−; procedure dec() . . . , σ[l = 1]
−→ private {x = l} within{inc() = {x = l}; −}; procedure dec() . . . , σ[l = 1]
−→3 private {x = l} within {inc() = {x = l}; −, dec(){x = l}; . . . , σ[l = 1]
−→ {inc(){x = l}; −, dec(){x = l}; . . . , σ[l = 1]
= ρ, σ[l = 1] , say
So we see that
ρ
c, σ −→∗ ρ0 ; (inc(); dec()), σ[l = 1]
and so we should examine the computation:
ρ[ρ0 ]
7.3
inc(); dec(), σ[l = 1]
−→ ({x = l}; x := x + 1); dec(), σ[l = 1]
−→∗ dec(), σ[l = 2]
−→ {x = l}; if x > 0 then x := x − 1 else nil, σ[l = 2]
−→∗ σ[l = 1] .
Other Parameter Mechanisms
Other parameter mechanisms can be considered in the same manner. The general principle is to
admit more ways to declare identifiers (as discussed above) and to admit more ways of evaluating
expressions (and/or actual expressions). The latter is needed because actual expressions can be
107
evaluated to various degrees when abstracts are called. One extreme is absolutely no evaluation
(see exercise 16 for this call-by-text mechanism). We shall first consider call-by-name in the
context of our applicative language which we regard as evaluating the argument to the extent of
binding the call-time environment to it; this well-known idea differs from the official ALGOL-60
definition and is discussed further in exercise 15.
Then we consider call-by-reference in the context of our imperative language where the argument is evaluated to produce a reference. Other mechanisms are considered in the exercises.
Note that in call-by-name for example the actual parameter may be further evaluated during
computation of the body of the abstract. It is even possible to have mechanisms (e.g., variants
of call-by-result) where some or all of the evaluation is delayed until after the computation of
the body of the abstract.
Call-by-Name
Syntactically it is only necessary to add another possibility for the formal parameters to the
syntax of our applicative language
form ::= name x : τ, form
Static Semantics
The sets of defining variables of name x : τ , form is clearly {x} ∪ DV(form). Regarding types
we add
aet ::= τ name, aet
dt ::= et | τ name | aet → et
The definition of the type T (form) of a formal needs the new clause
T (name x : τ, form) = τ name, T (form)
Here are the new predicate rules
• Formals: form : β =⇒ (name x : τ, form) : {x = τ name} ∪ β (if x ∈ DV(form))
• Expressions:
Variables: α V x : τ
(if α(x) = τ name)
This rule expresses the fact that if x is a call-by-name formal parameter as in name x : τ
then in the calling environment its denotation can be evaluated to a value of type τ .
α V e : et
α V ae : aet
• Actual Expr.:
α V e, ae : et name, aet
It is important to note that this rule is in addition to the previous rule. So given α an
actual expression can have several different types; these are needed as the same expression
can correspond to formals of different types, and that will require different kinds of evaluation.
108
Example 31 Consider these two expressions
let function fred (x : nat, name y : nat) : nat = x + y
in fred (u + v, u − v)
and
let function fred (name x : nat, y : nat) : nat = x + y
in fred (u + v, u − v)
In the first case we need the fact that α u + v, u − v : nat, nat name and in the second that
α u + v, u − v : nat name, nat (where α = {u = nat, v = nat}).
Dynamic Semantics
Clearly we must add a new component to the set of denotable values, corresponding to the new
denotable types τ name
DVal = Con + NExp + Abstracts
where we need NExp = {e : name τ } to allow free variables in the expressions because of the
possibility of recursive definitions. For example consider
rec name x : nat
= f (5) and
f (x : nat) = . . . f . . .
The extension to the definition of FV(dval) is, of course, clear
FV(e : name τ ) = FV(e)
For the predicates
V
V
dval : dt and α
V
dval we add the rules
α e:τ
α e : τ name
(e : τ name) : name τ
Transition Relations: For expressions and definitions we refine the usual ρ α e −→ e and
ρ α d −→ d a little, parameterising also on the set of variables whose definition is currently
available in the environment (the others will be in the process of being recursively defined). So
for α : V and W ⊆ V we will define the relations
ρ
α,W
e −→ e
and
ρ
α,W
d −→ d
def
where ρ : α W and e, e ∈ Γexp
α,W and d, d ∈ Γα,W where
Γexp
α,W = {e | ∃et. α
V
e : et}
109
Γdef
α,W = {d | α
V
d}
def
We also have the evident Texp
α,W and Tα,W .
For formals we have the predicate
ae
µ,W
form : ρ0
where ae ∈ Tµ,W and µ = M (T (form)).
For actual expressions the result desired will depend on the context and we introduce an
apparatus of different evaluation modes. The set Modes of modes is ranged over by µ given by
µ ::= · | value, µ | name, µ
Each actual expression type, aet, has a mode, M (aet) where
M (·) = ·
M (τ, aet) = value, M (aet)
M (τ name, aet) = name, M (aet)
We define transition relations ρ α,W,µ ae −→ ae which are also parameterised on modes. The
set of configurations is, for α : V , W ⊆ V and mode µ
Γα,W,µ = {ae | ∃aet. α
V
ae : aet and M (aet) = µ}
and we define the set Tα,W,µ of terminal actual expressions by some rules of the form
(1)
(2)
(3)
·,W
µ,W
T (ae)
T (·)
T (ae)
(value,µ),W T (con, ae)
µ,W T (ae)
(name,µ),W T (e, ae)
µ,W
(if FV(e) ∩ W = ∅)
It is rule 3 which introduces the need for W , insisting that all variables are bound, except,
possibly, for those being recursively defined.
The transition relation is defined for ρ : α W and ae, ae ∈ Γα,W,µ and has the form ρ α,W,µ
ae −→ ae . The apparatus of modes gives types what might also be called metatypes and this
may be a useful general idea. The reader should not confuse this with one normal usage of the
term mode as synonymous with type.
Transition Rules:
• Expressions: These are the same as before except for identifiers:
Identifiers: (1) ρ x −→ con
(if ρ(x) = con)
110
(2) ρ
x −→ e
• Actual Expr.
Value Mode: (1)
(if ρ(x) = e : τ name)
e −→ e
ρ α,(value,µ),W e, ae −→ e , ae
ρ
α,W
ae −→ ae
ρ α,(value,µ),W con, ae −→ con, ae
Name Mode: (1) ρ α,(name,µ),W e, ae −→ (let ρ FV(e) in e), ae
ρ
(2)
α,µ,W
ae −→ ae
(if FV(e) ∩ W = ∅)
ρ α,(name,µ),W e, ae −→ e, ae
• Definitions: Here we need a rule which ensures that the actual expressions are evaluated in
the right mode. Otherwise the rules are as before.
ρ α,µ,W ae −→ ae
Simple:
(1)
(where µ = M (T (form)))
ρ α,W form = ae −→ form = ae
ρ
(2)
(2)
α,µ,W
ae form : ρ0
ρ α,W form = ae −→ ρ0
(if ae ∈ Tα,µ,W where µ = M (T (form))
Formals:
(1) ·
(2)
(3)
·,W
·:∅
ae
con, ae
e, ae
(value,µ),W
(name,µ),W
form : ρ
(x : τ, form) : {x = con} ∪ ρ
µ,W
ae µ,W form : ρ
(x : τ name, form) : {x = e : τ name} ∪ ρ
Example 32 The main difference between call-by-name and call-by-value in applicative languages is that call-by-name may terminate where call-by-value need not. For example consider
the expression
e = let f (x : nat name) : nat = 1 and rec g(x : nat) : nat = g(x) in f (g(2))
Then ρ e −→∗ let ρ0 in f (g(2)) where ρ0 = {f (x : nat name) : nat = 1, g(x : nat) : nat =
. . .}. So we look at
ρ0
f (g(2)) −→ let x : nat name = g(2) in 1
−→∗ let {x = let g(x : nat) : nat = . . .} in 1
−→ 1
On the other hand if we change the formal parameter of f to be call-by-value instead, then, as
the reader may care to check, the evaluation does not terminate.
111
Call-by-Reference
We consider a variant (the simplest one!) where the actual parameter must be a variable (identifier denoting a location). In other languages the actual parameter could be any of a wide variety
of expressions which are evaluated to produce a location; these might include conditionals and
function calls. This would require a number of design decisions on the permitted expressions
and on how the type-checking should work. For lack of time rather than any intrinsic difficulty
we leave such variants to exercise 17. Just note that it will certainly be necessary to rethink
expression evaluation; this should either be changed so that evaluation yields a natural value
(be it location or primitive value) or else different evaluation modes should be introduced.
Syntactically we consider an extension to our imperative language
form ::= loc x : τ, form.
Static Semantics
Clearly we have DI(locx : τ, form) = {x} ∪ DI(form). For types we add another actual expression type
aet ::= τ loc, aet
and
T (ref x : τ, form) = τ ref , aet
and we have the rule
form : β
ref x : τ, form : {x = τ ref }, β
(if x ∈ I where β : I)
• Actual Expressions: These are as before with the addition
α
α
V
ae : aet
x, et : τ loc, aet
V
(if α(x) = τ loc)
It is here that we insist that actual reference parameters must be variables. As in the case of
call-by-name the type of an actual expression is not determined by its environment alone, but by
its context as well. (A more honest notation might be α, aet V ae rather than α V ae : aet.)
Dynamic Semantics
It is not necessary to change the definitions of DVal (or Env or Dec) as locations are already
included. However, we allow locations in AcExp and AeCon
ae ::= l, ae
112
acon ::= l, acon
and clearly FV(l, ae) = FV(ae) and we have the rule
α I ae : aet
α I l, ae : τ loc, aet
(l ∈ Locτ )
Transition Rules: We have relations ρ α e, σ −→ e , σ , ρ α d, σ −→ d , σ and
ρ α c, σ −→ c , σ (or σ ) and a predicate acon, L
form : ρ, σ as before. For actual
expressions we proceed as with call-by-name and introduce a set, Mode, of evaluation modes
µ ::= · | val, µ | loc, µ
with the evident definition of M (aet) ∈ Mode and put for α : I and µ,
Γα,µ = { ae, σ | ∃aet. α
Tα,µ = { acon, σ ∈ Γα,µ }
I
ae : aet and µ = M (aet)}
and will define the transition relation for ρ : α and µ
ρ
α,µ
ae, σ −→ ae , σ
Rules:
• Actual Expressions:
Value Mode: (1)
ρ
ρ
(1) ρ
(2)
α,(val,µ)
α,(val,µ)
α,(ref ,µ)
ρ
ρ
Simple:
(1)
(2)
Formals:
ρ
ρ
ae, σ −→ ae , σ
(con, ae), σ −→ (con, ae ), σ
α
(x, ae), σ −→ (l, ae), σ
α,µ
α,(ref ,µ)
• Definitions:
e, σ −→ e , σ
(e, ae), σ −→ (e , ae), σ
α
ρ
(2)
Ref. Mode:
ρ
(if ρ(x) = l)
ae, σ −→ ae , σ
(l, ae), σ −→ (l, ae ), σ
α
ρ α,µ ae, σ −→ ae , σ
form = ae, σ −→ form = ae , σ
α
acon, L form : ρ0 , σ0
form = acon, σ −→ ρ0 , σ ∪ σ0
(if µ = M (T (form)))
(where σ : L)
We just add a rule for declaration-by-reference (= location)
acon, L form : ρ0 , σ0
(l, acon), L loc x : τ, form : {x = l} ∪ ρ0 , σ0
Note: All we have done is to include the construct x == y of Chapter 3 in our simple
declarations.
• Commands: No new rules are needed.
113
Clearly our discussion of binding mechanisms is only a start, even granting the ground covered
in the exercises. I hope the reader will have been led to believe that a more extensive coverage
is feasible. What is missing is a good guiding framework to permit a systematic coverage.
7.4 Higher Types
Since we can define or declare abstractions, such as functions and procedures, Tennent’s Principle of Correspondence tells us that we can allow abstractions themselves as parameters of
(other) abstractions. The resulting abstractions are said to be of higher types (the resulting
functions are often called functionals). For example the following recursive definition is of a
function to apply a given function, f , to a given argument, x, a given number, t, of times:
rec Apply(f : nat −→ nat, x : nat, t : nat) : nat =
if t = 0 then x else Apply(f, f (x), t − 1)
We will illustrate this idea by considering a suitable extension of the imperative language of this
chapter (but neglecting call-by-reference). Another principle would be to allow any denotable
type to be an expressible type; this principle would allow locations or functions and procedures
as expressions and, in particular, as results of functions (by the Principle of Abstraction). For
example we could define an expression (naturally, called an abstraction)
λform. e
that would be an abbreviation for the expression let f (form) : τ = e in f . For a suitable
τ , depending on the context, it might, more naturally, be written as: function form. e; such
functions (and other similar abstractions) are often termed anonymous. Then the following
function would output the composition of two given functions
Compose(f : nat → nat, g : nat → nat) : nat → nat = λx : nat. f (g(x))
In this way we obtain (many) versions of the typed λ-calculus. A number of problems arise in
imperative languages where functions are not denotable, but only references to them. In the
definition of Compose one will have locally declared references to functions as the denotations
of f and g; if these are disposed of upon termination of the function call one will have a
dangling reference. Just the same thing happens, but in an even more bare-faced way, if we
allow locations as outputs
function f () : nat loc = let var x : nat = 5 in x
At any rate we will leave these issues to exercises, being moderately confident they can be
handled along the lines we have developed.
Now, let us turn to our language with higher types. We extend the syntax by including the
114
category AcETypes of actual expression types:
aet ::= · | τ, aet | (aet −→ τ ), aet | aet proc, aet
and then add to the stock of formals
form ::= function f : aet → τ, form | procedure p : aet, form
It is clear how this allows functions and procedures of higher type to be defined; they are passed
as arguments via identifiers that denote them.
Static Semantics
Clearly
DI(function f : aet −→ τ, form) = {f } ∪ DI(form)
DI(procedure p : aet, form) = {p} ∪ DI(form)
and
The definition of T (form) in AcETypes is also evident and we note
T (function f : aet → τ, form) = (aet → τ ), T (form)
T (procedure p : aet, form) = aet proc, T (form)
As for the predicate form : β we first note the definition of the set, DTypes, of denotable types:
dt ::= et | et loc | aet −→ et | aet proc
The rules are fairly clear and we just note the procedure case:
form : β
procedure p : aet, form : {p = aet proc}, β
(if p ∈ I where β : I)
Turning to the other predicates we only need to add a rule for actuals:
α
α
I
ae : aet
x, ae : dt, aet
I
(where dt = α(x) is either of the form aet → et or aet proc)
Example 33 Try type-checking the following imperative version of Apply in the environment
{x = nat}
function double(x : nat) : nat = 2 ∗ x
rec function apply(function f : nat → nat, x : nat, t : nat) : nat =
let var result : nat = x in
begin while t > 0 do begin x := f (x); t := t − 1 end
result result; end
x := apply(double, x, x)
115
Dynamic Semantics
Once more there is no need to change (the form of) the definitions of DVal or Env or Dec. We
must now allow abstracts within actual expressions and also AcCon
ae ::= (λform. e : τ ), ae | (λform. c), ae
acon ::= (λform. e : τ ), acon | (λform. c), acon
with the evident extensions to the definitions of FV(ae) and α
I
ae : aet.
Transition Relations: In the following α : I and J ⊆ I.
• Expressions: We define configurations and terminal configurations as usual; for the transition relation we define for ρ : α J
ρ
α,J
e, σ −→ e , σ
• Actual Expressions: We take
Γα,J = { ae, σ | FI(ae) ⊆ I}
and
Tα,J = { acon, σ | FI(acon) ∩ J = ∅}
and for ρ : α J the relation
ρ
α,J
ae, σ −→ ae , σ
• Declarations: We define Γα,J , Tα,J in the evident way, and the transition relation ρ α,J
d, σ −→ d , σ is of the evident form.
• Commands: Again the configurations, the terminal configurations and the transition relation are of the evident forms.
• Formals: We will define the predicate acon, L J form : ρ0 , σ0 where FI(acon) ∩ J = ∅.
Rules: Expressions, Declarations, Commands as before.
• Actual Expressions: As before, plus
ρ
α,J
(x, ae), σ −→ (abs, ae), σ
ρ
ρ
α,J
(if ρ(x) = abs ∈ Abstracts)
ae, σ −→ ae , σ
(abs, ae), σ −→ (abs, ae ), σ
α,J
• Formals: We just need two more rules
((λform. e : τ ), acon), L
J
acon, L J form : β
function f : aet → τ, form : {f = λform. e : τ }, β
116
(if f ∈ I where β : I)
((λform. e), acon), L
J
acon, L J form : β
procedure p : aet, form : {p = λform. e}, β
(if p ∈ I where β : I)
As a matter of fact the J’s are not needed, but we obtain finer control over the allowable actual
expression configurations. This can be useful in extensions of our language where abstractions
are allowed.
7.5 Modules and Classes
There is a certain confusion of terminology in the area of modules and classes. Rather than
enumerate the possibilities let me say what I mean here. First there is a Principle of Denotation
which says that one can in principle use an identifier to denote the value of any syntactic phrase
– where “value” is deliberately ambiguous and may indicate various degrees of “evaluation”.
For expressions this says we can declare constants (in imperative languages) but also allows
declaration by name or by text and so on; for commands it means we can have parameterless
subroutines. For declarations we take it as meaning one can declare identifiers as modules, and
they will denote the environment resulting from the elaboration. (There is a corresponding
Principle of Storeability which the reader will spot for himself; it is anything but clear how
useful these principles are!)
Applying the Principle of Abstraction to declarations on the other hand we obtain what we call
classes. Applying a class to actual arguments gives a declaration which can be used to supply
a denotation to a module identifier; then we say the module is an instance of the class. (Of
course everything we say here applies just as well to applicative languages; by now, however, it
is enough just to consider one case!)
A typical example is providing a random natural number facility. Let drand be the declaration
private
var a = seed mod d
within
function draw () : nat
begin a := a ∗ m mod d
result a/d end
where seed, d and m are assumed declared previously. This would declare a function, draw,
providing a random natural number with its own private variable – inaccessible from the outside.
If one wanted to declare and use two random natural numbers, just declare two modules
module X : draw : · → nat = drand
117
module Y : draw : · → nat = drand
begin . . . X.draw () . . . Y.draw () . . . end
Thus draw is an attribute of both X and Y and the syntax X.draw selects the attribute (in
general there is more than one).
When one wants some parameterisation and/or desires to avoid writing out drand several times,
one can declare and use a class
class random(const seed : nat, const d : nat) : draw : · → nat; drand ;
begin
..
.
module X : draw : · → nat = random(5, 2);
module Y : draw : · → nat = random(2, 3);
begin . . . X.draw () . . . Y.draw () . . . end
..
.
end
Finally we note that it is possible to use the compound forms of declarations to produce similar
effects on classes. For example a version of the SIMULA class-prefixing idea is available.
class CLASS1(form1) . . . ; . . . ;
class CLASS2(form2)—; —;
class PREFIXCLASS(form1, form2) . . . —;
CLASS1(form1); CLASS2(form2)
Naturally we will also be able to use simultaneous and private and recursive class declarations
(can you tell me some good examples of the use of these?). One can also easily envisage classes
of higher types (classicals?), but we do not investigate this idea.
Here is our extension of the syntax of the imperative language of the present chapter (but no
call-by-reference, or higher types).
• Types: We need the categories DTSpecs, AcETypes and DecTSpecs of denotable type specifications, actual expression types and declaration type specifications
dts ::= τ | τ loc | aet → τ | aet proc | dects | aet → dects
aet ::= · | τ, aet
dects ::= x : dts | x : dts, dects
Clearly dect will be the type of a module identifier and aet → dect will be the type of a class
identifier.
• Expressions: We add five(!!) new categories of expressions, function, procedure, variable,
module and class expressions, called FExp, PExp, VExp, MExp, CExp and ranged over by
118
f e, pe, ve, me, cle and given by the following productions (where we also allow f , p, v, m,
cl as metavariables over the set, Id, of identifiers)
f e ::= f | me.f
pe ::= p | me.p
ve ::= v | me.v
me ::= m | me.m | cle(ae)
cle ::= cl | me.cl
The definition of the set of expressions is extended by
e ::= me.x | f e(ae)
(and the second possibility generalises expressions of the form f (ae)). The set of actual
expressions is defined as before.
• Commands: We generalize commands of the forms p(ae) and x := e (i.e., procedure calls
and assignment statements) by
c ::= pe(ae) | ve := e
• Declarations: We add the following productions to the definition
d ::= module m : dects = d | class cl(form) : dects; d
Note that declaration types are used here to specify the types of the attributes of modules
and classes. If we except recursive declarations this information is redundant, but it could
be argued that it increases readability as the attribute types may be buried deep inside the
declarations.
• Formals: The definition of these remains the same as we do not want class or module
parameters.
Note: In this chapter we have essentially been following a philosophy of different expressions
for different uses. This is somewhat inconsistent with previous chapters where we have merged
different kinds of expressions (e.g., natural number and boolean) and been content to separate
them out again via the static semantics. By now the policy of this chapter looks a little ridiculous and it could well be better to merge everything together. However, the reader may have
appreciated the variation.
Static Semantics
For the definitions of FI(f e), . . . , FI(cle) we do not regard the attribute identifiers as free (but
rather as a different use of identifiers from all previous ones; their occurrences are the same
as constant occurrences and they are thought of as standing for themselves). So for example
119
FI(me) is given by the table
m
me.m
cle(ae)
FI {m} FI(me) FI(cle) ∪ FI(ae)
For the definitions of FI(e), FI(c) we put
FI(me.x)
= FI(me)
FI(f e(ae)) = FI(f e) ∪ FI(ae)
FI(pe(ae)) = FI(pe) ∪ FI(ae)
FI(ve := e) = FI(ve) ∪ FI(e)
and for FI(d) and DI(d) we have
module m : dect = d class cl(form) : dect; d
FI
FI(d)
FI(d)\DI(form)
DI
{m}
{d}
(We are really cheating somewhere here. For example the above scheme would not work if we
added the reasonable production
d ::= me
as then with, for example, a command m; begin . . . x . . . end the x can be in the scope of
the m if the command is in the scope of a declaration of the form module m : dect = var x :
nat = . . . ; . . .
Thus it is no longer possible to define the free identifiers of a phrase in a context-free way. Let
us agree to ignore the problem.)
• Types: We define (mutually recursively) the sets ETypes, FETypes, . . . , ClETypes, DTypes,
TEnv of expression types, function expression types, . . . , class expression types, denotable
types and type environments by
et ::= τ
f et ::= aet → τ
pet ::= aet proc
vet ::= τ loc
met ::= α
clet ::= aet −→ α
dt ::= et | vet | f et | pet | met | clet
120
TEnv = Id −→fin DTypes
(with α ranging over TEnv)
To see how the sets DTSpecs and DecTSpecs of denotable and declaration type specifications
specify denotable and declaration types respectively, we define predicates
dts : dt
and
dects : dect
by the formulae
- DTSpecs:
(1) τ : τ
(2) τ loc : τ loc
(3) aet → τ : aet → τ
(4) aet proc : aet proc
dects : α
(5)
(where the premise means proved from the rules for DecTSpecs)
dects : α
dects : α
(6)
aet → dects : aet → α
- DecTSpecs:
dts : α
(1)
(x : dts) : {x = α}
dts : α
dects : β
(if x ∈ I for β : I)
(x : dts, dets) : {x = α} ∪ β
Next T (form) ∈ AcETypes is defined as before. Now we must define the predicates
(2)
α
I
e : et
α
I
f e : f et, . . . , α
I
d:β
α
I
d form : β
I
cle : clet, α
I
c,
The old rules are retained and we add new ones as indicated by the following examples.
• Expressions:
α I me : β
(1)
(if β(x) = dt)
α I me.x : dt
α I f e : aet → et α I ae : aet
(2)
α I f e(ae) : et
• Function Expressions:
(1) α I f : f t
(if α(f ) = f t ∈ FTypes)
α I me : β
(if β(f ) = f t ∈ FTypes)
α I me.f : f t
• Class Expressions:
(1) α I cle : clet
(if α(cl) = clet ∈ ClETypes)
(2)
me : β
α I me.cl : clet
• Commands:
(2)
α
I
(if β(cl) = clet ∈ ClETypes)
121
(1)
α
I
α
I
pe : aet proc α
α I pe(ae)
I
ae : clet
vet : τ loc α I e : τ
α I (vet := e)
• Declarations:
- Modules:
dects : β
(1)
(module m : dects = d) : {m = β}
(2)
dects : β α I d : β
α I module m : dects = d
- Classes:
dects : β
(1)
(class cl(form) : dects; d) : {cl = T (form) −→ β}
(2)
(2)
dects : β form : α0 α[α0 ] I∪I0 d : β
α I class cl(form) : dects : d
(where α0 : I0 )
Dynamic Semantics
First we define the sets FECon, . . . , ClECon of function expression constants, . . . , class
expression constants by
f econ ::= λform. e : et
pecon ::= λform. c
vecon ::= l
mecon ::= ρ
clecon ::= λform. d : β
and also add the productions
f e ::= f econ, . . . , cle ::= clean | d
and define the sets DVal and Env of denotable values and environments by
dval ::= con | vecon | f econ | pecon | clecon | mecon
Env = Id −→fin DVal
and extend the definition of declarations by the production
d ::= ρ
These are mutually recursive definitions of a harmless kind. The extensions to the definition of
FI(f e), . . . , FI(de), FI(d), DI(d) are evident; for example FI(λform. d : β) = FI(d)\DI(form).
We must also extend the definitions of α I f e : f et, . . . , α I cle : clet and I d : β and α
(the latter two in the case d = ρ). The former extensions are obvious; for example
122
I
d
• Class Abstracts:
form : α0 α[α0 ] I∪I0 d : β
α I (λform. d : β)
For the latter we have to define
I
(where α0 : I0 )
decon : dt and this also presents little difficulty; for example
• Class Abstracts:
(λform. d : β) : T (form) → β
I
Then we have the two rules
∀x ∈ I0
(1)
I
∀x ∈ I0
(2)
ρ(x) : β(x)
ρ:β
I
α
α
I
I
(where ρ : I0 )
ρ(x) : β(x)
ρ
Transition Relations: The set, Stores, is as before.
The configurations, final configurations and the transition relations for expressions, actual expressions and declarations are as before; for formals we have the same predicate as before. Now
fix α : I and ρ : α J (for some J ⊆ I).
• Function Expressions: We take Γα = { f e, σ | ∃f et. α I f e : f et}, Tα = { f econ, σ |
∃f et. α I f econ : f et} and the transition relation has the form ρ I γ −→ γ
The definitions for PExp, . . . , CExp are the analogues of that for function expressions
Rules:
• Class Expressions:
(1) ρ α cl, σ −→ clecon, σ
(2)
(3) ρ
me, σ −→ me , σ
me.cl, σ −→ me .cl, σ
ρ
ρ
α
α
α
(if ρ(cl) = clecon)
ρ0 .cl, σ −→ clecon, σ
(if ρ0 (cl) = clecon)
The rules for FExp, . . . , MExp are similar except that in the last case we need also
(1)
(2) ρ
(3)
ρ
ρ
α
ρ
ρ
α
cle, σ −→ cle , σ
cle(ae), σ −→ cle (ae), σ
α
(λform. d : β)(ae), σ −→ private form = ae within d, σ
α
α
d, σ −→ d , σ
d, σ −→ d , σ
(where in the top line we mean a transition of Decl)
123
The new rules for expressions and commands should be clear; for example
• Assignment:
ρ σ ve, σ −→ ve , σ
(1)
ρ α ve := e, σ −→ ve := e, σ
ρ
(2)
ρ
(3) ρ
α
α
e, σ −→ e , σ
l := e, σ −→ l := e , σ
α
l := con, σ −→ σ[l = con]
For declarations the new rules are
• Modules:
(1)
ρ
α
ρ α d, σ −→ d , σ
module m : dects = d, σ −→ module m : dects = d , σ
(2) ρ α module m : dects = ρ0 , σ −→ {m = ρ0 }, σ
• Classes:
ρ α class cl(form) : dects; d −→ {cl = λform. (ρ\I) in d} (where I = DI(form))
7.6
Exercises
1. Consider dynamic binding in the context of a simple applicative language so that, for
example,
let x = 1; f (y) = x + y
in let x = 2 in f (3)
has value 5. What issues arise with type-checking? Can you program iterations (e.g.,
factorial) without using recursive function definitions?
2. In a maximalist solution to the problem (in the applicative language) of neatly specifying
functions of several arguments one could define the class of formal parameters by
form ::= · | x : τ | form, form
and merge expressions and actual expressions, putting
e ::= · | e, e | f (e)
and amending the definition of definitions
d ::= form = e | f (form) : τ = e
a) Do this, but effectively restrict the extension to the minimalist case by a suitable choice
of static semantics.
124
b) Allow the full extension.
c) Go further and extend the types available in the language by putting
τ ::= nat | bool | τ, τ | ·
thus allowing tuples to be denotable.
3. Consider the maximalist position in a simple imperative programming language.
4. Consider in a simple imperative language how to allow expressions on the left-hand of
assignments:
e0 := e1
and even the boolean expression e0 ≡ e1 which is true precisely when e0 and e1 evaluate
to the same reference. As well as discussing type-checking issues, try the two following
approaches to expression evaluation:
a) Expressions are evaluated to their natural values which will be either locations or basic
values.
b) Modes of evaluation are introduced, as in the text.
Extend the work to the maximalist position where actual expressions and expressions are
merged, thus allowing simultaneous assignments.
5. Just as expressions are evaluated, and so on, formals are matched (to given actual values)
to produce environments (= matchings). The semantics given above can be criticised as
not being dynamic enough as the matching process is not displayed. Provide an answer
to this; you may find configurations of the form
form, con, ρ
useful where form is the formal being matched, con is the actual value and ρ is the
matching produced so far. A typical rule could be
x : τ, con ρ −→ ρ ∪ {x = con}
This is all for the applicative case; what about the imperative one? Investigate dynamic errors, allowing constants and repeated variables in the formals (dynamic error = matching
failure).
6. In the phrase rec d all identifiers in R = FV(d) ∩ DV(d) are taken to be recursively
defined. Investigate the alternative rec x1 , . . . , xn .d where {x1 , . . . xn } ⊆ R.
7. In some treatments of recursion to evaluate an expression of the form
let rec (f (x) = . . . f . . . g . . . and g(x) = — f — g —) in f (5)
125
one evaluates f (5) in the environment
ρ = {f (x) = . . . f . . . g . . . , g(x) = — f — g —}
(ignoring free variables) and uses the simple transition:
ρ
f (5) −→ let x = 5 in . . . f . . . g . . .
I could not see how to make this simple and nice idea (leave the recursively defined
variables free) work in the present setting where one has nested definitions and binary
operations on declarations. Can you make it work?
8. Try some examples of the form
let rec (f (x) = . . . f . . . g . . . & g(x) = — f — g —) in e
where & is any of ;, and or in.
9. Consider the following recursive definitions of constants:
a) rec x : nat = 1
b) rec (y : nat = 1 and x : nat = y)
c) rec (x : nat = y and y : nat = 1)
d) rec (x : nat = x)
e) rec (x : nat = y and y : nat = x)
How are these treated using the above static and dynamic semantics? What do you
think should happen? Specify suitable static and dynamic semantics with any needed
error rules. Justify your decisions, considering how your ideas will extend to imperative
languages with side-effects (which might result in non-determinism).
10 Find definitions d0 and d1 to make different as many as possible of the following definitions:
a) (rec d0 ; d1 )
b) (rec) (rec d0 ; d1 )
c) (rec) (d0 ; rec d1 )
d) (rec) (rec d0 ; rec d1 )
where (rec) d indicates the two possibilities with and without rec.
11. Check that the first alternative for type-checking recursive definitions would work in the
126
sense that
α
V
d:β
iff
V
d : β and α
V
d
12. Programming languages like PASCAL often adopt the following idea for function definition:
function f (form) : τ
begin
c
end
where within c the identifier f as well as possibly denoting a function also denotes a
location, created on function entry and destroyed on exit; the result of a function call is
the final value of this location on exit. For example the following is an obscure definition
of the identity function:
rec function f (x : nat) : nat
begin
f := 1;
if x = 0 then f := 0
else f := f + f (x − 1)
end
Give this idea a semantics.
13. Call-by-need. In applicative languages this is a “delayed evaluation” version of call-byname. As in call-by-name the formal is bound to the unevaluated actual, with the local
environment bound in the closure. However, when it is necessary for the first time to
evaluate the actual, the formal is then bound to the result of the evaluation. Give this idea
a semantics. One possibility is to put (some of) the environment into the configurations,
treating it like a store. Another is to bind the actual to a new location and make the
actual the value of that location in a store. Prove call-by-need equivalent to call-byname. Consider delayed evaluation variants of parameter mechanisms found in imperative
languages.
14. Call-by-name. Consider (minimalist & maximalist) versions of call-by-name in imperative
127
programming languages. Look out for the dangers inherent in
procedure f (x : nat name) =
begin
..
.
x := · · ·
..
.
end
15. Discover the official ALGOL 60 definition of call-by-name (it works via a substitution
process); give a semantics following the idea and prove it equivalent to one following the
idea in these notes (substitution = binding a closure).
16. Call-by-text. Give a semantics for call-by-text where the formal is bound to the actual
(not binding in the current environment); when a value is desired the actual is evaluated
in the then current environment. Consider also more “concrete” languages in which the
abstract syntax (of the text) is available to the programmer, or even the concrete syntax:
does the latter possibility lead to any alteration of the current framework?
17. Call-by-reference. Give a maximalist discussion of call-by-reference, still only allowing actual reference parameters to be variables. Extend this to allow a wider class of expressions
which (must) evaluate to a reference. Extend that in turn to allow any expression as an
actual; if it does not evaluate to a reference the formal should be bound to a new reference
and that should have the value of the actual.
18. Call-by-result. Discuss this mechanism where first the actual is evaluated to a reference, l;
second the formal is bound to a new reference l (not initialised); third, after computation
of the body of the abstract, the value of l is set to the value of l in the then current store.
Discuss too a variant where the actual is not evaluated at all until after the body for the
abstract. [Hint: Use declaration finalisation.]
19. Call-by-value-result. Discuss this mechanism where first the actual is evaluated to a reference l; second the formal is bound to a new reference l which is initialised to the current
value of l; third, after the computation of the abstract of the body, the value of l is set
to the value of l in the then current store.
20. Discuss selectors which are really just functions returning references. A suitable syntax
might be
selector f (form) : τ = e
which means that f returns a reference to a τ value. First consider the case where all
128
lifetimes are semi-infinite (extending beyond block execution). Second consider the case
where lifetimes do not persist beyond the block where they were created; in this case
interesting questions arise in the static semantics.
21. Consider higher-order functions in programming languages which may return abstracts
such as functions or procedures. Thus we add the syntax:
e ::= λform. e | λform. c
The issues that arise include those of lifetime addressed in exercise 20.
22. Here is a version of the typed λ-calculus
τ ::= nat | bool | τ −→ τ
e ::= m | t | x | e bop e | if e then e else e |
let x : τ = e in e | e(e) | λx : τ. e
Give a static semantics and two dynamic semantics where the first one is a standard
one using environments and where the second one is for closed expressions only and uses
substitutions as discussed in the exercises of Chapter 3. Prove these equivalent. Add a
recursion operator expression
e ::= Y
with the static semantics α V Y : (τ −→ τ ) −→ τ (τ = nat, bool) and a rule something
like ρ Y e0 −→ e0 (Y e0 ). What does this imply about formals which are of functional
type and their evaluation, and why is that important?
129
A
A Guide to the Notation
Syntactic Categories
Truthvalues
Numbers
Constants
Actual Constants
Unary Operations
Binary Operations
t∈T
m, n ∈ N
con ∈ Con
acon ∈ ACon
uop ∈ Uop
bop ∈ Bop
Variables
Identifiers
v, f ∈ Var
V ⊆fin Var
x, f, p, m, cl ∈ Id
I ⊆fin Id
Expressions
Boolean
Actual
Variable
Function
Procedure
Module
Class
e ∈ Exp
b ∈ BExp
ae ∈ AExp
ve ∈ VExp
f e ∈ FExp
pe ∈ PExp
me ∈ MExp
cle ∈ CExp
Commands
(=Statements)
c ∈ Com
Definitions/
Declarations
d ∈ Def/Dec
Formals
Types
Expression
Actual Expr.
Denotable
Type Spec.
Declaration
Type Spec.
Static Semantics
Free Variables/
Identifiers
Defined Variables/
Identifiers
form ∈ Forms
τ ∈ Types
et ∈ ETypes
aet ∈ AETypes
dts ∈ DTSpecs
dects ∈ DecTSpecs
FV/I(e), FI(c), FV/I(d) etc.
DV/I(d) DV/I(form)
130
Denotable Types
Type Environments
Example Formulae
Dynamic Semantics
Denotable Values
Environments
Storeable Types
Locations
Storeable Values
Stores
dval ∈ DVal
ρ ∈ Env (e.g., = Id −→fin DVal)
st ∈ STypes
I ∈ Loc = st Locst
L ⊆fin Loc
sval ∈ SVal = st Valst
σ ∈ Stores (e.g., = {σ ∈ Loc −→fin SVal |
∀st ∈ STypes. σ(Locst ) ⊆ SValst })
µ ∈ Modes
Γ, T, −→
γ∈Γ
where Γ is the set of configurations
T ⊆ Γ is the set of final configurations
γ −→ γ is the transition relation
Evaluation Modes
Transition Systems
Example
Configurations
Example Final
Configurations
Example Transition
Relations
B
dt ∈ DTypes
α, β ∈ TEnv (e.g., = Id −→fin DTypes)
α V e : et α I c α I d : β
form : β T (form) = aet
e, σ ; c, σ , σ; d, σ
con, σ ; σ; ρ, σ
ρ
ρ
ρ
I,µ
I
I
e, σ −→ e , σ
c, σ −→ c , σ /σ
d, σ −→ d , σ /ρ
Notes on Sets
We use several relations over and operations on sets as well as the (very) standard ones. For
example X ⊆fin Y means X is finite and a subset of Y .
Definition 34 Let Op(X1 , . . . , Xn ) be an operation on sets. It is monotonic if whenever X1 ⊆
X1 , . . . , Xn ⊆ Xn we have Op(X1 , . . . , Xn ) ⊆ Op(X1 , . . . , Xn ). It is continuous if whenever
X11 ⊆ X12 . . . ⊆ X1m ⊆ . . . is an increasing sequence and . . . and Xn1 ⊆ Xn2 ⊆ . . . ⊆ Xnm ⊆ . . . is
an increasing sequence then
X1m , . . . ,
(∗) Op(
m
Xnm ) =
m
Op(X1m , . . . , Xnm )
m
Note: Continuity implies monotonicity. Conversely to prove continuity, first prove monotonic131
ity. This establishes the ⊇ half of (∗); then prove the ⊆ half.
Example 35
• Cartesian Product:
X1 × . . . × Xn = { x1 , . . . , xn | x1 ∈ X1 and . . . and xn ∈ Xn }
is monotonic and continuous. Prove this yourself.
• Disjoint Sum:
def
X1 + . . . + Xn = ({1} × X1 ) ∪ . . . ∪ ({n} × Xn )
i∈A
Xi
def
=
i∈A {i}
× Xi
Show that the finite sum operation is continuous. (Finite Sum is just union, but forced to be
disjoint.)
• Finite Functions: The class of finite functions from X to Y is
X →fin Y =
A→Y
A⊆fin X
Note that the union is necessarily disjoint. Show that →fin is continuous.
For A ⊆fin X if f ∈ A → Y ⊆ X →fin Y (we identify f with A, f ) we write f : A. This is
used for environments (including type environments) and stores. There are two useful unary
operations on finite functions. Suppose that f : A and B ⊆ A. Then the restriction of f to B
is written f B, and defined by:
(f
B)(b) = f (b) (for b in B)
Note that f B : B. For any C ⊆ X we also define f
C = f (A ∩ C).
There are also two useful binary operations. For f : A and g : B in X →fin Y we define
f [g] : A ∪ B by
f [g](c) =
g(c) (c ∈ B)
f (c) (c ∈ A\B)
and in case A ∩ B = ∅ we define f, g : A, B (also written f ∪ g) by:
f, g(c) =
f (c) (c ∈ A)
g(c) (c ∈ B)
Note this is a special case of the first definition, but it is very useful and worth separate mention.
132
The Importance of Continuity
Suppose Op(X) is continuous and we want to find an X solving the equation
X = Op(X)
Put X 0 = ∅ and X m+1 = Op(X m ). Then (by induction on m) we have for all m, X m ⊆ X m+1
and putting X = m X m
Op(X) = Op(
m
X m)
=
m
Op(X m )
=
m
X m+1
(by continuity)
=X
And one can show (do so!) that X is the least solution – that is if Y is any other than X ⊆ Y .
Indeed X is even the least set such that Op(X) ⊆ X.
This can be generalised, suppose Op1 (X1 , . . . , Xn ), . . . , Opn (X1 , . . . , Xn ) are all continuous and
we want to solve the n equations
X1 = Op1 (X1 , . . . , Xn )
..
.
Xn = Opn (X1 , . . . , Xn )
Put Xi0 = ∅ for i = 1, . . . , n and define
Xim+1 = Opi (X1m , . . . , Xnm )
Then for all m and i, Xim ⊆ Xim+1 (prove this) and putting
Xim
Xi =
m
we obtain the least solutions to the equations – if Yi are also solutions then for all i, Xi ⊆ Yi .
Indeed the Xi are even the least sets such that Opi (Xi , . . . , Xn ) ⊆ Xi (i = 1, . . . , n). This is
used in the example below. Prove this.
Example 36 Suppose we are given sets Num, Id, Bop and wish to define sets Exp and Com
by the abstract syntax
e ::= m | x | e0 bop e1
e ::= x := e | c0 ; c1 | if e0 = e1 then c0 else c1 | while e0 = e1 do c
133
Then we regard this definition as giving us set equations
Exp = Num + Id + (Exp × Bop × Exp)
Com = (Id × Exp) + Com × Com + (Exp × Exp × Com × Com) + (Exp × Exp × Com)
and also giving us a notation for working with the solution to the equations. First m is identified
with 1, m ∈ Exp and x is identified with 2, x in Exp. Next
e0 bop e1
= 3, e0 , bop, e1
x := e
= 1, x, e
c0 ; c1
= 2, c0 , c1
if e0 = e1 then c0 else c1 = 3, e0 , e1 , c0 , c1
while e0 = e1 do c0
= 4, e0 , e1 , c0
Now the set equations are easily solved using the above techniques as they are in the form
Exp = Op1 (Exp, Com)
Com = Op2 (Exp, Com)
where Op1 (Exp, Com) = Num + Id + (Exp × Bop × Exp) and Op2 is defined similarly. Clearly
Op1 and Op2 are continuous as they are built up out of (composed from) the continuous disjoint
sum and product operations (prove they are continuous). Therefore we can apply the above
techniques to find a least solution Exp, Com. Note that Exp and Com are therefore the least
sets such that
1. Num ⊆ Exp and Id ⊆ Exp (using the above identifications).
2. If e0 , e1 are in Exp and bop is in Bop then e0 bop e1 is in Exp.
3. If x is in Id and e is in Exp then x := e is in Com.
..
4. . . .
.
..
5. . . .
.
6. If e0 , e1 are in Exp and c is in Com then while e0 = e1 do c is in Com.
At some points in the text environments (and similar things) were mutually recursively defined
with commands and so on. This is justified using our apparatus of continuous set operators
employing, in particular, the finite function operator.
134