15-150 Fall 2014
Lecture 3
Stephen Brookes
Specifications and proofs
Last time
Expression evaluation produces a value
e => v means e evaluates to v
Declarations produce value bindings
d => [ x :v , ..., x :v ]
Matching a pattern to a value
(if it terminates)
1 1
k k
either succeeds with bindings, or fails
TYPE SAFETY
Substitution
For bindings [ x1:v1, ..., xk:vk ] and expression e
we write
[ x1:v1, ..., xk:vk ] e
for the expression obtained by substituting
v1 for x1, ..., vk for xk
in e
(for free occurrences, only)
[ x:2 ] (x + x)
is
(2 + 2)
[ x:2 ] (fn y => x + y)
is
(fn y => 2 + y)
[ x:2 ] (fn x => x + x)
is
(fn x => x + x)
Evaluation
(defined by rules)
+ evaluates left-to-right
If
e1 =>* v1 and e2 =>* v2
then
*
*
e1 + e2 => v1 + e2 => v1 + v2
(only allow well-typed instances of rules)
Evaluation
Functions
a function call evaluates its argument
If
e1 =>* (fn x => e) and e2 =>* v
then
e1 e2 =>* (fn x => e) e2
*
*
=> (fn x => e) v => [x:v]e
Declarations
In the scope of
fun f(x) = e
f =>* (fn x => e)
Example
fun f(x) = if x=0 then 1 else f(x-1)
In scope of this declaration:
f(1-1)
=>* (fn x => if x=0 then 1 else f(x-1)) (1-1)
=>* (fn x => if x=0 then 1 else f(x-1)) 0
=>* if 0=0 then 1 else f(0-1)
=>* if true then 1 else f(0-1)
=>* 1
(justified by the rules given earlier!)
Specifications
For a function definition, specify (* as comments *)
name and type of the function
a requires-condition
- assumptions about the argument
an ensures-condition
- guarantees about the result function calls,
when the argument has required properties
Example
fun f(x) = if x=0 then 1 else f(x-1)
fun f(x:int):int = if x=0 then 1 else f(x-1)
(* f : int -> int
*)
(* REQUIRES x 0
*)
(* ENSURES f x =>* 1 *)
(* f : int -> int
*)
(* REQUIRES x 0 *)
(* ENSURES f x = 1 *)
*)
(* f : int -> int
*)
(* f : int -> int
*)
(* REQUIRES y 0
*)
(* REQUIRES true
(* ENSURES f y =>* 1 *) (* ENSURES
if x 0 then f x =>* 1 *)
Notation
=>* is the reflexive transitive closure of =>
(one-step evaluation)
=>+ is the transitive closure of =>
e => e
e
*
=>
e =>+ e
one step
finitely many steps
at least one step
e when value v such that e =>* v
when e has an infinite evaluation
Example
fun f(x:int):int = f x
In scope of this declaration,
f 0 =>+ (fn x => f x) 0
=>* [x:0] f x
*
=>
So
f0
f 0 =>+ f 0 and (f 0)
Inversion
(facts about evaluation)
Arithmetic
If
e1 + e2 =>* v (a value)
there are values v1, v2 such that
e1 =>* v1
e2 =>* v2
v1 + v2 =>* v
+ evaluates its arguments
Inversion
Evaluation
If
e1 e2 =>* v
there are x, e, v2 such that
e1 =>* fn x => e
e2 =>* v2
*
[ x:v2 ] e => v
Patterns
If matching p to v succeeds with [ x :v , ..., x :v
1 1
(fn p => e) v
k k
],
=>* [ x1:v1, ..., xk:vk ]e
If
matching p1 to v fails,
and matching p2 to v succeeds with [ x1:v1, ..., xk:vk ],
(fn p1 => e1 | p2 => e2) v
*
=> [ x1:v1, ..., xk:vk ]e2
(and so on)
So far
Using evaluational notation like => and =>*
we can talk precisely about program behavior
But sometimes we may want to ignore
evaluation order...
For all expressions e1, e2 : int and all values v:int,
if e1 + e2 =>* v then e2 + e1 =>* v
In such cases, equational specs may be better
For all e1, e2 : int,
e1 + e 2 = e 2 + e 1
says the same,
more succinctly
Example
fun add1(x, y) = x + y
fun addr(x, y) = y + x
Let E be a well-typed expression of type int
containing a call to addl
Let E be obtained by changing to addr
E is also well-typed with type int
If E =>* 42 then also E =>* 42
addl and addr are indistinguishable
Not easy to prove directly using =>*
Equality
(extensional equivalence)
For each type t there is a mathematical notion
of equality for expressions of that type
e1 =int e2
n. (e1 =>* n iff e2 =>* n)
f1 =int->int f2
(f1 iff f2) and
e1,e2:int. (e1 =int e2 implies f1 e1 =int f2 e2)
addl =int->int addr
Compositionality
(when well-typed)
Substitution of equals
If e = e and e = e
1
then (e1 e1) = (e2 e2)
If e
= e2 and e1 = e2
then (e1 + e1) = (e2 + e2)
1
and so on
fun add1(x, y) = x + y
fun addr(x, y) = y + x
Let E be a well-typed expression of type int
containing a call to addl
Let E be obtained by changing to addr
Easy to show that addl = addr
By compositionality, E = E
Hence, if E =>* 42 then also E =>* 42
int->int
int
Easy to prove using =
Equations
(when well-typed)
Arithmetic
e+0=e
e1 + e 2 = e 2 + e 1
e1 + (e2 + e3) = (e1 + e2) + e3
21 + 21 = 42
Boolean
if true then e1 else e2 = e1
if false then e1 else e2 = e2
(0 < 1) = true
Equations
(when well-typed)
Applications
only when
argument
is a value
(fn x => e) v = [x:v]e
Declarations
In the scope of
fun f(x) = e
the equation
f = (fn x => e)
holds
Equations
(when well-typed)
Applications
(fn p1 => e1 | p2 => e2) v = [B1]e
if
match(p1, v) succeeds with bindings [B1]
(fn p1 => e1 | p2 => e2) v = [B2]e
if
match(p1, v) fails
& match(p2, v) succeeds with bindings [B2]
Equations
Declarations
In the scope of
fun f(p1) = e1 | f(p2) = e2
the equation
f = (fn p1 => e1 | p2 => e2 )
holds
Extensionality
When e
and e2 are values of type t -> t
e1 = e2
if and only if
for all values v1, v2 of type t
v1 = v2 implies e1 v1 = e2 v2
Useful facts
e
=>
e =>* v implies e = v
*
v implies (fn x => E) e = [x:v] E
evaluation
is consistent with
equivalence
So far
Can use equivalence or = to specify the
applicative behavior of functional programs
Equality is compositional
Equality is defined in terms of evaluation
=> is consistent with = and ML evaluation
*
Specifications
Be clear and precise
Use bound variables consistently
Use => (evaluation) and = (equality)
*
accurately and consistently
Dont leave any assumptions hidden
Example
(revisited)
fun f(x:int):int = if x=0 then 1 else f(x-1)
(* f : int -> int
(* REQUIRES x 0
(* ENSURES f x = 1
f x = 1
means the same as
f x =>* 1
*)
*)
*)
eval spec
fun eval ([ ]:int list) : int = 0
| eval (d::L) = d + 10 * (eval L);
(* eval : int list -> int *)
!
(* REQUIRES:
*)
(* every integer in L is a decimal digit *)
(* ENSURES:
*)
(*
eval(L) evaluates to a non-negative integer *)
Does it work?
A specification asserts a correctness property
about the applicative behavior of a function
How do we prove that a function
satisfies a specification?
Prove that the function ensures a correct result
whenever applied to an argument that meets
the requirements
Why bother? Testing is not general enough!
Example
fun f(x:int):int = if x=0 then 1 else f(x-1)
(* f : int -> int
*)
(* REQUIRES x 0
*)
(* ENSURES f x = 1 *)
Same as
For all non-negative integer values x,
f x =>* 1
Example
We already showed that
f 0 =>* (fn x => ) 0 =>* 1
Its easy to show then that
f 1 =>* (fn x => ) 1
=>* f (1-1) =>* (fn x => ) 0 =>* 1
And then its easy to show that
f 2 =>* (fn x => ) 2
=>* f (2-1) =>* (fn x => ) 1 =>* 1
(how about a complete proof?)
Proofs
We give math-based proofs
using equational or evaluational reasoning
Use the program structure as a guide
program syntax
reasoning
if-then-else
case analysis on a boolean
case p of ...
case analysis on a value
fun f(x) = ...f...
induction
Induction
A family of proof techniques
simple (mathematical) induction
complete (strong) induction
structural induction
well-founded induction
Our plan
Introduce induction
templates to help write accurately
learn when applicable
Focus on examples
Specifications will involve
equality and evaluation
Simple induction
To prove a property of the form
P(n), for all non-negative integers n
First, prove P(0).
Then show that, for all k 0,
base case
P(k+1) follows logically from P(k).
inductive step
Why it works
P(0) gets a direct proof
P(1) follows from P(0)
P(2) follows from P(1)
Similarly, for each n0 we can show P(n)
for k>0, at the k step weve already
th
shown P(k), so P(k+1) follows logically
Example
fun f(x:int):int = if x=0 then 1 else f(x-1)
(* REQUIRES x0
(* ENSURES f(x) = 1
To prove:
For all values x:int
such that x0, f(x) = 1
*)
*)
Proof by simple induction
Let P(n) be f(n) = 1
Base case: we prove P(0), i.e. f(0) = 1
f 0 = (fn x => if x=0 then 1 else f(x-1)) 0
= [x:0](if x=0 then 1 else f(x-1))
= if 0=0 then 1 else f(0-1)
= if true then 1 else f(0-1)
=1
So f(0) = 1
Proof by simple induction
Recall P(n) is f(n) = 1
Inductive step:
let k0, assume P(k), prove P(k+1).
Let v be the numeral for k+1.
f(k+1) = if v=0 then 1 else f(v-1)
= if false then 1 else f(v-1)
= f(v-1)
=fk
since v=k+1
=1
by assumption P(k)
So P(k+1) follows from P(k)
Using simple induction
Q: When can I use simple induction to
prove a property of a recursive function f ?
A: When there is a non-negative measure of
argument size and f(x) only makes recursive
calls of form f(y) with size(y) = size(x)-1
Example
fun eval ([ ]:int list) : int = 0
| eval (d::L) = d + 10 * (eval L);
length of argument list
decreases by 1 in recursive call
To prove:
For all values L:int list
there is an integer n such that
eval L =>* n
When it doesnt work
fun decimal (n:int) : int list =
if n<10 then [n]
else (n mod 10) :: decimal (n div 10)
You cannot use
simple induction!
Why not?