Programming Languages (CSC-302 98S)
Outline of Class 38: Semantics of Scheme
Held: Wednesday, May 6, 1998
- I should have homework six graded for Friday.
- The final is next Monday. It is cumulative. A review sheet will be
ready on Friday, and we'll spend at least part of Friday's class on
- Let's develop a denotational semantics for SIMPLE. Our semantics
will need to accommodate the various utterances in the language.
- We begin with the syntactic domains. These correspond to the
nonterminals of our grammar.
V in Val Numeric constants
I in Id Variables
E in Exp Expressions
S in Sta Statements
L in SL Statement Lists
P in Prog Programs
- We continue with an abstract syntax
Prog ::= L
SL ::= S
| S ; L
Stat ::= I = E
| if E then L fi
| if E then L0 else L1 fi
| while E do L od
Exp ::= E0 + E1
| E0 * E1
| E0 - E1
| E0 / E1
- Note that this is somewhat different than our traditional syntax.
This is an abstract syntax and assumes that the details (such as
associativity) are worked out in the concrete syntax. Note also
that we use variables on the right hand side and sets on the left.
- We'll need a meaning function for each of the syntactic domains.
We'll call these
- What are the semantic domains we'll need? Certainly input (a list of
numbers), output (a list of numbers and "error"s), the environment
(a map from identifiers to numbers or "undefined"), and perhaps some
N Natural numbers
U = Id -> N Environments
I = N* Input
O = N* Output
- We certainly need to consider the appropriate type of our functions.
- What is the type of an evaluated program? A program should be
a function from input to output.
meaningProg :: Prog -> I -> O
- What is the type of an evaluated statement? That might be a little
bit harder. In order to evaluate a statement, we need the environment
and may need the input. After evaluating a statement, we may have
affected environment, output, and input.
- How do we "pass them back" for the next statement?
- We could return a triplet.
- We could evaluate the next statement in the updated environment
- We'll try the second. This means that the meaning functions for
statements must take "something" as a third parameter. Hmmm ... that
thing is "rest of program", which seems awfully like "continuation".
C = U -> I -> O
- Now let's again consider the type of
meaningStat. To determine
the meaning of a statement, we need its environment, input, and
continuation. The result is output.
meaningStat : Stat -> U -> I -> C -> O
- Okay, what about statement lists? From our abstract and concrete syntax,
we know that a statement list can be a statement or a statement
followed by a statement list. They can also appear in while loops and
conditionals. We also know that statement lists
relate to programs. Since they'll need to read input and write output
using an environment, perhaps with
meaningSL : SL -> C-> U -> I -> -> O
- Why did I change the place of the continuation? Just for variety.
- We're left with expressions. What is the type of
meaningExp? It might be useful to specify what happens
with the expression, or it might be useful just to give a value. In
either case, we need the environment (since the expression may contain
variables). Rather than using continuations, we'll just have
the meaning of an expression be a value.
meaningExp : Exp -> U -> N
- Note that we need an environment so that we can look up identifiers.
- We are now ready to write our semantic functions.
- The meaning of a program is the meaning of the corresponding statement
list when run on the basic environment.
meaningProg[[SL]] = meaningSL[[SL]] cleanup basicEnv
basicEnv : U
basicEnv = \x . undefined
cleanup = \env . \inp . nil
- Note that I'm using
\var . body
lambda var . body
- Where's the input? It's hidden, because everything is curried. We
could just as easily have written
meaningProg[[SL]] = \inp . meaningSL[[SL]] cleanup basicEnv inp
- What is the meaning of a statement list? It depends on whether it's
a single statement or an actual list. In the first case, the meaning
is simply the output of the statement.
input and environment.)
\cont . \env . \inp . meaningS[[S]] env inp cont
- Note that if I'd chosen a better order for the arguments to the
statementlist meaning function, I might have been able to write
a simpler definition.
- What is the meaning of a statement list with multiple statements?
It's fairly simpmle
\cont . \env . \inp . meaningS[[S]] env inp meaningSL[[L]]
- Now we can worry about the individual statements.
- What is the meaning of input? An update to the environment. Note that
to update the environment, we'll need a conditional and some list selection
- We'll use the McCarthy conditional,
t -> a, b,
which means "if t then a else b".
- We'll just use
\env . \inp . \cont .
cont (setenv env I (car inp)) (cdr inp)
setenv : U -> Id -> N -> U
setenv env id num =
\x . (x == id) -> num , (env id)
- What is the meaning of output? We'll assume there is a convenient
cons operation. (See the Scheme specification for a
more formal version.)
\env . \inp . \cont .
cons (meaningExp[[E]] env) (cont env inp)
- Assignment is similar to a combination of the two.
meaningStat[[I = E]] =
\env . \inp . \cont .
cont (setenv env I (meaningExp[[E]] env)) inp
- What about loops? Loops can be more complicated for a number of
- The definition is even more recursive than most.
- Not all loops terminate.
- We'll handle recursion the "quick and dirty" way. We're describing
a function, not defining it. Any function that meets our criteria
is acceptable (although we prefer the least such function).
- Nontermination is a more subtle issue. In effect, a program that
doesn't terminate doesn't meet our requirements for the meaning
function (generating a list of numbers). What do we do? We should
update our requirements (including, perhaps, cons) so that
"nonterminating" is a legal result. [This is left as an exercise
for the reader.]
meaningStat[[while E do L od]] = F(E,L) s.t.
F(E,L) :: U -> I -> C -> O
F(E,L) env inp cont =
((meaningExp[[E]] env) == 0) -> cont env inp,
meaningSL(L) env inp (\e . \i . F(E,L) e i cont)
- As you can tell from reading the Scheme report, the formal semantics
of Scheme is quite small.
- This is partially because it's an elegant set of definitions.
- This is partially because more complex Scheme builtins can be
automatically translated to Scheme primitives, so we don't need
a large set of primitives.
- The semantics is designed for people used to reading semantics and
thinking functionally. Novices may find that it takes awhile to
plow through it.
- At the same time, it is useful as a begnning point in reading
- Reading the section on semantics (section 7.2 in the Revised (5)
report), you will note that it contains
- A description of the notation used (or selected parts of that notation;
lambdas aren't mentioned even though they are used).
- A short commentary on general issues intented to introduce the
- An abstract syntax
- A set of domain equations defining the primary domains over which
the semantic functions operate.
- A list of types for semantic functions.
- Definitions of the semantic functions.
- Definitions of "helper" functions used to define the semantic functions.
- There are a number of notations we must concern ourselves with in the
- There is the notation introduced at the beginning of section 7.2.
- There are many sequence-based operations.
This suggests that much of the definition of Scheme will be sequence-based
(as you might expect). The semantics will take advantage of sequence
creation, destruction (select elements and removing initial elemnts),
length, and concatenation.
- In addition, we will use conditionals (a frequently needed device)(
- We will also use injection (moving from subset to superset) and
project (from superset to subset).
- Elsewhere in the section, you will see lambdas used frequently.
- These are curried lambdas, so "lambda x y . whatever" is shorthand for
"lambda x . lambda y . whatever".
- You may observe that the particular symbols used for variables
are important. In particular, the name of a variable indicates its
type (not a traditional design in languages, but useful).
- You should also observe that different fonts and styles are used to
indicate different roles.
- Roman words are used for syntactic domains.
- Capital Greek letters are used for variables reperesenting elements
of those domains).
- Monospace words are used for syntactic elements.
- Monospace letters are used for semantic domains.
- Lowercase Greek letters are used for elements of semantic domains.
- Script letters are used for semantic functions.
- Script words are used for the helper functions.
- The abstract syntax of Scheme should be somewhat self-evident.
- There are only four "kinds" of things in Scheme programs:
constants, identifiers, expressions, and commands.
- In fact, commands and expressions are more-or-less the same
- There are only a few kinds of expressions. They are
- Application of an expressoin to one or more expressions
- Three kinds of lambda abstractions
- Two kinds of conditionals
- One side-effecting operator
- What kinds of lambdas are there?
- Functions with a particular number of arguments.
- Functions written to handle an arbitrary number of arguments.
- Functions in which the aguments are contained in the body.
- Note that each lambda abstraction permits a sequence of "commands"
before ending with an expression that represents the value of the
- You might want to note that the abstract syntax is ambiguious.
This is because "they details are in the concrete syntax". In
particular, parsing is treated as a separate issue.