Chapter 1 abstract syntax
Programming languages describe computations for humans and machines.
Syntax explains how phrases form programs, but "syntax" actually has layers:
Concrete (surface) syntax: how code is written/displayed as character strings (ASCII/Unicode).
-> Ignored.
Abstract (structural) syntax: the program's tree structure (ASTs) showing how phrases are built from operators and subphrases.
Binding structure: how identifiers are declared and used-binding and scope-captured by enriching ASTs into abstract binding trees (ABTs).
-> Defines precise operations/relations on ABTs to formalize binding and scope - topics that are tricky, bug-prone in implementations.
Abstract syntax trees (ASTs)
General
An AST is an ordered tree:
Leaves: Variables/Operators.
Interior nodes: Operators.
Children: Arguments.
An AST is classified into sorts corresponding to different forms of syntax.
A variable stands for an unspecified, or generic, piece of syntax of a specified sort.
Operator:
Can be used to combine ASTs.
Have an arity specifying the sort of the operator & the number and sorts of its arguments.
Example: An operator of sort and arity combines AST's of sort , respectively, into a compound AST of sort .
Variables
Definition: An unknown object drawn from a domain.
The unknown can become known by substitution of a particular object for all occurrences of a variable in a formula.
-> Specializing a general formula to a particular instance.
Sorts
Abstract syntax tree (AST)s are classified by sorts that divide ASTs into Syntactic categorys.
Example 1
Familiar programming languages often have a syntactic distinction between expressions and commands.
-> 2 sorts of abstract syntax trees.
-> Variables in abstract syntax trees range over Sorts in the sense that only AST's of the specified sort of the variable can be plugged in for that variable.
-> It would make no sense to replace an expression variable by a command, nor a command variable by an expression, the two being different sorts of things.
Example 2
A language of arithmetic expressions built from numbers, addition, multiplication.
The abstract syntax:
An operator of sort for each number .
Two operators, and , each of sort , each with two arguments of sort .
-> has the abstract syntax .
-> If has sort , then also has sort .
The tree structure of Abstract syntax tree (AST) allows Structural induction.
Suppose that we wish to prove that some property holds for all ASTs of a given sort. To show this, it is enough to consider all the ways in which can be generated and show that the property holds in each case under the assumption that it holds for its constituent ASTs (if any).
Precise definitions
: A finite set of Sorts.
An Arity has the form , which specifies the sort of an Operator taking arguments, each of sort .
Let be an arity-indexed family of disjoint sets of operators of arity .
If is an Operator of Arity , we say that has Sort and has arguments of sorts .
Let be a sort-indexed family of disjoint finite sets of variables of sort . When is clear from context, we say that:
- A variable is of sort if .
- A is fresh for , or just fresh when is understood, if for any sort .
If is fresh for and is a sort, then is the family of sets of variables obtained by adding to .
The notation is ambiguous in that the sort is not explicitly stated but determined from context.
The family of abstract syntax trees of sort is the smallest family satisfying the following conditions:
- A variable of sort is an AST of sort : if , then .
- Operators combine ASTs: if is an operator of arity , and if , then .
Variables are given meaning by substitution.
If and , then is the result of substituting for every occurrence of in .
The AST is called the target, and is called the subject, of the substitution.
Substitution is defined by the following equations:
- and if .
- .
TIP
Theorem. If , then for every there exists a unique such that .
Proof
Base step:
- , with .
Then, .
-> Trivially hold.
- .
Then, .
-> Trivially hold.
Inductive step: .
Then, , where are unique.
-> Hold.
Abstract binding trees (ABTs)
General (ABTs)
Abstract binding tree (ABT) enrichs AST with the means to introduce new variables and symbols (Bindings), with a specified range of significance (Scope).
The scope of a binding is an ABT within which the bound identifier can be used, either as a place-holder (in the case of a variable declaration) or as the index of some operator (in the case of a symbol declaration).
The set of active identifiers can be larger within a subtree of an ABT than it is within the surrounding tree.
Different subtrees may introduce identifiers with disjoint scopes.
-> The crucial principle is that any use of an identifier should be understood as a reference, or abstract pointer, to its binding.
-> The choice of identifiers is immaterial, so long as we can always associate a unique binding with each use of an identifier.
Renaming of bound variables is constrained to the extent that it must not alter the reference structure of the expression.
Example
The expression has a different meaning than the expression , because the in the expression in the second case refers to the inner declaration, not the outer one as before.
ABT = AST + binding + scope.
Allow an operator to bind any finite number (possibly zero) of variables in each argument.
An argument to an operator is called an Abstractor and has the form .
The sequence of variables are bound within the ABT .
(When is zero, we elide the distinction between and itself)
Example: The expression has the form .
-> Clearly specifies that the variable is bound within , and not within .
We often write to stand for a finite sequence of distinct variables and write to mean .
To account for binding, operators are assigned generalized arities of the form , which specifies operators of sort with arguments of valence .
A valence has the form , which specifies:
The sort of an argument.
The number and sorts of the variables bound within it.
-> A sequence of variables is of sort = The two sequences have the same length and that the variable is of sort for each .
The operator has arity .
-> is of sort whose:
- First argument is of sort and binds no variables.
- Second argument is also of sort and within which is bound one variable of sort .
Example: = .
Precise definitions (ABTs)
Fix a set of sorts and a family of disjoint sets of operators indexed by their generalized arities.
For a given family of disjoint sets of variables , is the family of ABTs.
This is surprisingly hard to make precise.
The first definition attempt
is the least family of sets closed under the following conditions:
If , then .
For each operator of arity , if , ..., and , then .
-> Almost correct, but fails to account for renaming of bound variables: If , then we cannot introduce another .
-> An ABT of the form is ill-formed according to this definition, because the first binding adds to , which implies that the second cannot also add to , because it is not fresh for .
-> Goal: Ensure that each of the arguments is well-formed regardless of the choice of bound variable names.
-> Achieved using fresh renaming - a bijection between sequences of variables.
A fresh renaming (relative to ) of a finite sequence of variables is a bijection between and , where is fresh for .
-> is the result of replacing each occurrence of in by , its fresh counterpart.
is the least family such that:
If then .
For each operator of arity , if for each and each fresh renaming , we have , then .
-> The renaming of each ensures that collisions cannot occur and that the ABT is valid for almost all renamings of any bound variables that occur within it.
The principle of structural induction extends to ABTs and is called Structural induction modulo fresh renaming.
It states that to show that holds for every , it is enough to show the following:
- If , then .
- For every of arity , if for each , holds for every with , then .
Subtlety
How does this handle free variables in subterms?
Question:
Consider . When applying structural induction at the outer , we need the induction hypothesis to hold for the subterm . But in this subterm, is free. How does the principle account for free variables that arise when we descend under binders?
Answer:
The property is parameterized by a set of variables , written . This parameter tracks which free variables are "permitted" in the terms under consideration.
When you descend under the binder , the principle applies a fresh renaming where . The induction hypothesis then states that holds for the renamed subterm .
The key insight: the extended parameter set now includes , legitimizing its occurrence as a free variable in the subterm. So the induction hypothesis isn't required to hold for subterms with arbitrary free variables-only for subterms whose free variables are contained in the extended set .
This is precisely what the notation expresses: the property relativized to a variable set that has been extended with the fresh renamings of the bound variables.
α-equivalence: The relation means that and are identical up to the choice of bound variable names.
The α-equivalence relation is the strongest congruence containing the following two conditions:
.
if for every , for all fresh renamings and .
The idea is that:
- We rename and consistently to common fresh names, avoiding confusion.
- We check that and are α-equivalent.
If , then and are α-variants of each other.
Substitution: The substitution of an ABT of sort for free occurrences of a variable of sort in some ABT , written , is partially defined by:
, and if .
, where for each , we require that , and we set if , and otherwise.
Pitfall 1 - bound variables block substitution
If is bound by an abstractor within , then does not occur free within the abstractor and hence is unchanged by substitution.
For example, , there being no free occurrences of in .
Pitfall 2 - variable capture
If and , then is undefined, rather than being .
For example, provided that , is undefined, not , which confuses two different variables named .
Avoiding capture: Capture can always be avoided by first renaming the bound variables in to avoid any free variables in .
Example: If we rename the bound variable to to obtain , then is defined and equals .
The price is that substitution is only determined up to α-equivalence.
The identification convention: Abstract binding trees are always identified up to α-equivalence.
That is, α-equivalent ABTs are regarded as identical.
Substitution can be extended to α-equivalence classes by choosing representatives such that substitution is defined, then forming the equivalence class of the result.
Any two valid choices give α-equivalent results, so substitution becomes a well-defined total function.
Symbols:
Motivation: It will often be necessary to consider languages whose abstract syntax cannot be specified by a fixed set of operators but rather requires that the available operators be sensitive to the context in which they occur.
A symbolic parameter, or symbol, indexes families of operators.
An indexed operator is a family of operators indexed by symbols , so that is an operator when is an available symbol.
If is a finite set of symbols, then is the family of ABTs generated by operators and variables, admitting all indexed operator instances by symbols .
A variable is a placeholder that stands for an unknown ABT of its sort.
A symbol does not stand for anything and is not itself an ABT.
The only significance of a symbol is whether it is the same as or differs from another symbol.
The set of symbols is extended by introducing a new, fresh symbol within a scope using the abstractor , which binds the symbol within the ABT .
The only difference between symbols and variables is that the only operation on symbols is renaming; there is no notion of substitution for a symbol.
Notation convention: Arguments to operators can be visually grouped using braces and parentheses (e.g., ), with stages progressing right to left. This is purely a readability aid-it has no semantic significance. The grouped form is identical to the flat form .
Historical notes:
- Abstract syntax originates with Church, Turing, and Gödel, who first considered programs acting on representations of programs.
- Programs were initially encoded as natural numbers via Gödel-numberings based on prime factorization (Kleene, 1952).
- Lisp introduced a more practical representation as symbolic expressions (McCarthy, 1965; Allen, 1978).
- ML added a type system capable of expressing abstract syntax trees (Gordon et al., 1979).
- AUTOMATH introduced using Church's λ notation to handle binding and scope (Church, 1941; Nederpelt et al., 1994), developed further in LF (Harper et al., 1993).
- Abstract binding trees here were inspired by NuPRL's notation (Constable, 1986) and Martin-Löf's system of arities (Nordström et al., 1990).
- Symbol binders are influenced by (Pitts and Stark, 1993).
Exercises
Exercise 1.1
Problem statement. Prove by structural induction on abstract syntax trees that if , then .
Proof
Consider an AST . We will prove that .
- If then .
- If , then by the induction hypothesis, . By definition, .
Therefore, .
Exercise 1.2
Problem statement. Prove by structural induction modulo renaming on abstract binding trees that if , then .
Proof
Given two sets of variables and , for an ABT , denote .
We'll prove that implies .
We perform induction on the structure of :
- If , it's obvious that , for all .
- Suppose .
Consider a fresh renaming relative to both and .
It's trivial to see that and .
The induction hypothesis is that , or .
By definition, .
Exercise 1.3
Problem statement. Show that if and and both and are defined, then .
Proof
By definition, and are defined.
- If , then .
Therefore, .
- If and , then .
Therefore, .
- If , then , such that for all , for all fresh renamings and .
Because is defined, we have , and similarly, .
Therefore, , where if , or otherwise.
Similarly, , where if , or otherwise.
By induction, .
Exercise 1.4
Problem statement. Bound variables can be seen as the formal analogs of pronouns in natural languages. The binding occurrence of a variable at an abstractor fixes a "fresh" pronoun for use within its body that refers unambiguously to that variable (in contrast to English, in which the referent of a pronoun can often be ambiguous). This observation suggests an alternative representation of ABTs, called abstract binding graphs, or ABGs for short, as directed graphs constructed as follows:
(a) Free variables are atomic nodes with no outgoing edges.
(b) Operators with arguments are -ary nodes, with one outgoing edge directed at each of their children.
(c) Abstractors are nodes with one edge directed to the scope of the abstracted variable.
(d) Bound variables are back edges directed at the abstractor that introduced it.
Notice that ASTs, thought of as ABTs with no abstractors, are acyclic directed graphs (more precisely, variadic trees), whereas general ABTs can be cyclic. Draw a few examples of ABGs corresponding to the example ABTs given in this chapter. Give a precise definition of the sort-indexed family of abstract binding graphs. What representation would you use for bound variables (back edges)?