Theory of Programming Languages
Learning Objectives
- You know where to find supplementary material for programming language theory.
- You know some of the common notation used in programming language theory, such as inference rule and evaluation notation.
- You know how inference rules are used to define the relations between different constructions in programming languages.
- You know how formation trees are used to define abstract data types.
- You know how a simple proof of an evaluation relation is constructed.
Introduction
Classically, the study of programming languages is split into syntax and semantics, where syntax refers to the grammar and the syntactic rules of the language, whereas semantics refers to the meaning and validity of sentences in the language. However, modern theory has moved on from studying the formal structure into something more abstract where programs are considered to be mathematical objects governed by statics and dynamics.
- Statics define features of the programming language, such as how terms and programs are formed and how the terms are typed.
- The dynamics define how these features behave at “runtime”, i.e. during evaluation or execution.
The dynamics can rely on the invariants placed by the statics, such as “variable
x
is an integer”, to provide safe (and usually deterministic) behavior for the programs.
A well designed programming language usually shifts weight to the statics of the language, as its properties are (closer to being) mathematically provable. Well designed programs in these languages use the statics (mostly the type system) to bear more of the weight of the business logic1 so that you get the feeling that “if it compiles, it works”2. Very few programming languages actually manage to cross this “line of confidence” — and those which do, the author claims, have strong type systems.
For a more thorough look at programming language theory, have a look at the following textbooks
-
Types and Programming Languages by Benjamin C. Pierce.
A thorough and approachable book that covers almost all currently relevant programming language properties. This book was the primary inspiration for this whole course.
-
Practical Foundations for Programming Languages by Robert Harper.
A bit more logically thorough approach to programming languages than Types and Programming Languages. The notation and terminology used on this course closely matches this book.
-
Essentials of Programming Languages by Friedman and Wand (free).
A very concrete approach to designing programming languages. The book uses scheme, a member of the LISP programming language family, for all of the implementatino code.
-
Introduction to the Theory of Programming Languages by Bertrand Meyer (free).
Covers all the basics very thoroughly and uses a common knowledge concepts for examples.
-
Programming Language Foundations (free).
A one hundred percent formalized and machine-checked book about developing the simply typed lambda calculus from scratch using a proof assistant.
The most approachable textbook is Types and Programming Languages (in the author’s opinion) after which the next logical step is Practical Foundations for Programming Languages
All of these resources are recommended supplementary reading, but understanding the theory of programming languages thoroughly is not required on this course.
Programming languages were mostly studied through formal systems with an underlying logical system. Logic and computation remained mostly separate until the logicians Haskell Curry and William Howard realized that code and mathematics are deeply connected.
Nowadays programming languages are not only studied through meta-theories based on logic, but are also used as a foundations of mathematics1 to study other programming languages2 or even areas of mathematics3!
Inference Rules
The logical notation used and developed in this chapter is at least somewhat standard in programming language (and mathematical) literature.
Inference rules, also known as transformation rules, are a construction from formal logic which allows us to clearly express the rules governing relations. Inference rules consist of a set of premises and a conclusion and when written down, the premises and conclusion are visually separated by a horizontal line. The premises and conclusion are judgments which could be thought of as “statements”.
For example, the premises and could be transformed into the conclusion , for any well-formed4 propositions . This inference rule is commonly known as modus ponens and can be visually written as:
The and and possibly other variables present in inference rules are called metavariables and they can be replaced with anything that meets the “type signature” of the surrounding operators and relations.
For extra reading, see Introduction to the Theory of Programming Languages, Chapter 9.2.6.
Formation Rules
A formal language is divided into nonsense and well-formed formulas.
The suffix “-F” refers to a formation rule.
E.g. the term has the following formation tree:
Evaluation Rules
Evaluation rules will be used to analyze the dynamics5 of programming languages. The word dynamics refers to how the language behaves during evaluation or “execution” — it is unrelated to the concept of dynamic programming languages or dynamic programming.
An evaluation rule is simply a relation between a term and a value. The reason we want a relation is that evaluation is not always a function. For example a random number generator is non-deterministic, and therefore not functional, but it still is a relation and we can reason about the outcomes in a mathematical way.
We can turn the function from chapter 4 into a relation very easily, so let’s do that now and define a relation for our arithmetic language:
The double arrow pointing down is often used when defining big-step operational semantics for a language.
Let’s spell out the evaluation rule for :
This is read as: “under no assumptions, evaluates to .”
This rule can be instantiated for any value of the metavariable , meaning that all the following evaluation rules exist:
Next, let’s think about what the evaluation rule for looks like. Let’s start with the conclusion — we want to express that addition evaluates to the sum of the values that the subterms evaluate to. Call these values and , and we can conclude (under the to be determined assumptions)
Well it’s quite simple to see that the assumption should say something about and or otherwise the evaluation rule would conclude that any evaluates to any . The reasonable assumptions would therefore be and and we get:
Again, we can think of as metavariables which can be replaced with any value of the appropriate type. For example an instance of the evaluation rule could be:
Which reads: “if evaluates to and evaluates to 8, then evaluates .”
This is of course logically valid, even though the assumptions are false in this case as doesn’t hold. Here we see how the assumptions are a way to constrain the values of the metavariables so that some relations hold between them.
Finally, the evaluation rules for and are similar:
It’s very important to note that these are all the rules for evaluation (for this language), or the evaluation relation is the closed under these rules. This means that we can say exactly which rule was/is used during evaluation.
Proof Trees
Tying all of this together, we can examine how the evaluation rules determine the value of a term. Let’s start with e.g. . Our goal is to construct a proof tree using the evaluation rules, such that at the top of the tree, there are no explicit assumptions left.
A proof tree shows the complete picture of every evaluation rule used to form the statement. Proof trees are not only about mathematical propositions — they can also be used to describe how a sentence in some grammar is constructed according to BNF derivation rules (called formation rules in formal logic).
First, to match the evaluation rule for , we need to replace by and by .
We leave the values as metavariables hoping that we find a value for them later.
Next, we need to create subtrees for both of the assumptions. Which rule matches ? The rule!
In order to use the rule, we simply draw a horizontal line over both .
Our proof tree is not yet finished as there are assumptions left. The assumption needs a proof of its own. Here we should use the rule, but doesn’t look like a multiplication… This is in fact not a problem, we introduce two new metavariables (yes we can do that) and say . Now we can just write as a multiplication of the two integers.
Here is the incomplete proof tree for :
Now we just prove both assumptions using the rule:
Putting the proofs together and replacing with , we get a tree that looks like this:
The only thing left is to replace by its value , by and by , resulting in:
The reason every step worked out so smoothly was because of the fact that our relation was defined using a function (a functional and total relation). This fact made every subtree uniquely determined (by functionality) and ensured there was at least one evaluation rule we could use to progress the subtree (totality).
Footnotes
Footnotes
-
Idris, a language for type driven development, type safe zero-cost abstractions in Rust ↩ ↩2
-
Annals of Formalized Mathematics, a journal that publishes articles about formalized mathematics and mathematical applications of proof assistants. ↩
-
Well-formed means that the formula is syntactically valid, i.e. derived from the formation rules of the language of propositional logic. ↩
-
See Practical Foundations for Programming Languages, chapters 5 and 7. ↩