-
Notifications
You must be signed in to change notification settings - Fork 15
Types and Programming Languages Chapter 3 Untyped Arithmetic Expressions
Programming languages are an engineering invention, but they can be studied mathematically. If we use mathematical language to describe the syntax and execution of a simple programming language, we can use mathematical techniques to investigate and analyse it. By applying operational semantics, we can detect certain classes of errors in programs without running them.
Familiarity with the following set notation is required for the chapter:
- ∅
- the empty set
- {
foo
,bar
}- the set containing two elements,
foo
andbar
- the set containing two elements,
-
t1
∈T
-
t1
is an element of the setT
-
- {
foo
,bar
} ⊆T
- the set containing
foo
andbar
is a subset of the set T (i.e.foo
andbar
are both members of T)
- the set containing
- {
foo
,bar
} ∪T
- the union of the set containing
foo
andbar
and the setT
- the union of the set containing
There are several mathematical ways to describe the possible expressions (“terms”) of a programming language. Unless the language is very simple, the set of all possible terms is infinitely large. That infinite set can either be indirectly characterised as containing all the terms that satisfy certain rules, or more directly constructed as the limit of an infinite sequence of explicit finite sets of terms.
Because the number of terms is usually infinite, we can’t prove anything about “all terms” by actually checking every term. Instead we need mathematical techniques, like proof by induction, which allow us to prove things about all possible terms simultaneously.
There are several mathematical ways to describe the execution behaviour of a programming language. TAPL chooses a technique called “operational semantics” which treats a term as the state of an imaginary machine; a single step of the machine turns the term into some new term, and it may take many steps of the machine for it to reach a final state (if it ever does). Operational semantics mathematically captures the execution of a programming language by defining the rules of this imaginary machine.
The machine’s rules are written as two-dimensional “inference rules” which describe both the prerequisites and the result of a particular machine step. We can arrange these rules into trees to show how a specific term is affected by a single step of the machine.
We can nominate certain kinds of terms (e.g. true
and false
) as “values”, and say that it’s an error when the machine has a non-value as its current term but can’t take any more steps. (We will see later that the point of a type system is to allow us to predict these errors without actually running the machine.)
For each natural number i, define a set Si as follows…
-
@tomstuart: Here’s a little Ruby interpretation of definition 3.2.3:
require 'set' terms = Set.new i = 0 loop do puts "iteration #{i}" puts "terms = #{terms.entries}" gets # hit enter to continue terms = Set.new(['true', 'false', '0']) + terms.flat_map { |t| ["succ (#{t})", "pred (#{t})", "iszero (#{t})"] } + terms.flat_map { |t1| terms.flat_map { |t2| terms.flat_map { |t3| "if #{t1} then #{t2} else #{t3}" } } } i += 1 end
In which context, exercise 3.2.5 means “prove that this ☝️ program has the same output if you change line 12 to
terms +=
”.
If
t
is in normal form, thent
is a value.
-
@tomstuart: Notice that this is only true because the language has booleans and nothing else, so it happens to be impossible to “make a mistake” when you write a term.
The condition in an
if … then … else …
term inevitably evaluates to a boolean because that’s the only kind of thing there is, so either E-IfTrue or E-IfFalse must eventually apply, and both rules dismantle theif … then … else …
and replace it with a smaller expression.If the language supported other kinds of thing (e.g. numbers) then this wouldn’t be true any more: a badly-written
if … then … else …
term with a non-boolean condition doesn’t have an evaluation rule, so it’s in normal form, but it isn’t a value.
First, we choose some well-founded set S and give a function f mapping “machine states” (here, terms) into S.
-
@tomstuart: “well-founded set” means “you always run out of smaller elements” (see 2.2.10 on page 18).
For example, the natural numbers are a well-founded set because zero is smaller than every other natural number. (An example of a non–well-founded set is the integers, because there is no smallest integer: -1 is smaller than 0, but -2 is even smaller, -3 is smaller again, and so on.)
The termination proof assumes you realise that a) the size of a term is a natural number, and b) the natural numbers are well founded. In other words: if you can prove that evaluation always reduces the size of a term, then evaluation must eventually finish because the size can’t go below zero.
-
@tuzz: I watched this video which helped me understand this idea, although I'm not sure if it's technically the same thing.
- Home
- Documentation
- Choosing a Topic
- Shows & Tells
- Miscellaneous
- Opt Art
- Reinforcement Learning: An Introduction
- 10 Technical Papers Every Programmer Should Read (At Least Twice)
- 7 More Languages in 7 Weeks
- Lua, Day 1: The Call to Adventure
- Lua, Day 2: Tables All the Way Down
- Lua, Day 3
- Factor, Day 1: Stack On, Stack Off
- Factor, Day 2: Painting the Fence
- Factor, Day 3: Balancing on a Boat
- Elm, Day 1: Handling the Basics
- Elm, Day 2: The Elm Architecture
- Elm, Day 3: The Elm Architecture
- Elixir, Day 1: Laying a Great Foundation
- Elixir, Day 2: Controlling Mutations
- Elixir, Day 3: Spawning and Respawning
- Julia, Day 1: Resistance Is Futile
- Julia, Day 2: Getting Assimilated
- Julia, Day 3: Become One With Julia
- Minikanren, Days 1-3
- Minikanren, Einstein's Puzzle
- Idris Days 1-2
- Types and Programming Languages
- Chapter 1: Introduction
- Chapter 2: Mathematical Preliminaries
- Chapter 3: Untyped Arithmetic Expressions
- Chapter 4: An ML Implementation of Arithmetic Expressions
- Chapter 5: The Untyped Lambda-Calculus
- Chapters 6 & 7: De Bruijn Indices and an ML Implementation of the Lambda-Calculus
- Chapter 8: Typed Arithmetic Expressions
- Chapter 9: The Simply-Typed Lambda Calculus
- Chapter 10: An ML Implementation of Simple Types
- Chapter 11: Simple Extensions
- Chapter 11 Redux: Simple Extensions
- Chapter 13: References
- Chapter 14: Exceptions
- Chapter 15: Subtyping – Part 1
- Chapter 15: Subtyping – Part 2
- Chapter 16: The Metatheory of Subtyping
- Chapter 16: Implementation
- Chapter 18: Case Study: Imperative Objects
- Chapter 19: Case Study: Featherweight Java
- The New Turing Omnibus
- Errata
- Chapter 11: Search Trees
- Chapter 8: Random Numbers
- Chapter 35: Sequential Sorting
- Chapter 58: Predicate Calculus
- Chapter 27: Perceptrons
- Chapter 9: Mathematical Research
- Chapter 16: Genetic Algorithms
- Chapter 37: Public Key Cryptography
- Chapter 6: Game Trees
- Chapter 5: Gödel's Theorem
- Chapter 34: Satisfiability (also featuring: Sentient)
- Chapter 44: Cellular Automata
- Chapter 47: Storing Images
- Chapter 12: Error-Correcting Codes
- Chapter 32: The Fast Fourier Transform
- Chapter 36: Neural Networks That Learn
- Chapter 41: NP-Completeness
- Chapter 55: Iteration and Recursion
- Chapter 19: Computer Vision
- Chapter 61: Searching Strings
- Chapter 66: Church's Thesis
- Chapter 52: Text Compression
- Chapter 22: Minimum spanning tree
- Chapter 64: Logic Programming
- Chapter 60: Computer Viruses
- Show & Tell
- Elements of Computing Systems
- Archived pages