This is the introductory reading for classes 7 & 8.
The deadline for exercises in this reading was Tuesday, September 29 at 9pm.
Recommended reading in Program Proofs: Chapter 5. Lemmas and Proofs sections 5.7—5.9, Chapter 7. Unary Numbers, and Chapter 8. Sorting.
In class 7, we will be continuing to work with linked lists. In class 8, we will work with a more elaborate type for representing mathematical expressions, and it is that Expr type we introduce in this reading.
Example: arithmetic expressions
Let’s begin with an inductive datatype for some simple mathematical expressions:
That is, an expression is either an integer constant, a named variable, or a sum of two expressions.
As you did in 6.101 when you evaluated symbolic expressions and/or interpreted LISP programs, explain how to evaluate an Expr given a State that assigns values to variables:
dfy
type State = map<string, int>
(That is, a State is a map of string keys to int values.)
For now, evaluating an expression that uses a variable not defined in the state should produce some arbitrary integer value.
In the exercise above, example.dfy is a “test case” whose correctness Dafny is able to prove. As with all of our verification, the program is not being run! Dafny is repeatedly unfolding functions and expanding definitions in order to attempt a proof. This strategy is more likely to work for expressions that require only a small number of unfoldings, and it is less likely to work as the computation grows in complexity. For sufficiently intricate examples, even if they are correct, Dafny will no longer be able to prove their correctness because it does not manage to complete its “simulation” of the computation.
Nevertheless, when we’re not sure if our definitions are working as intended, constructing concrete examples and checking their behavior can give us confidence.
Example: free variables
Hopefully you are grumpy about the -42 in our solution above and in general about Eval’s specification in the face of undefined Var names. The natural thing to do is restrict evaluation to expressions and states where every variable has a value in the state.
First, complete the implementation of function FreeVars to compute the set of free variables in an expression: variables that are used without definition.
Right now all variables are free variables because our expressions have no way to bind variables to values. This language isn’t LISP yet!
Look at example.dfy for another “test case” whose correctness Dafny should be able to prove.
And now, which could we use as the precondition of Eval:
dfy
function Eval(expr: Expr, state: State): int // ???{ match expr case Const(value) => value case Var(name) => state[name] // no more -42 case Plus(left, right) => Eval(left, state) + Eval(right, state)}
Example lemma: same value
We can use these last two definitions of Eval and FreeVars to prove a simple property of expression evaluation by induction.
We want to characterize when changes to the state don’t affect expression evaluation. Complete this lemma proof by adding a good precondition using the recursive functions we defined.
You probably don’t want to mention Eval directly, which could lead to a vacuous and not-at-all-useful lemma statement!
And you shouldn’t need to give Dafny a body in order to prove the lemma.
Summary
Inductive type definitions are often associated with recursive functions, which then become handy to use in preconditions and postconditions. Whenever you notice a verification isn’t going through because some property (“obvious” or not) about an inductive type isn’t being taken into account, consider formalizing the property with a recursive function and adding it to related specifications.