0% found this document useful (0 votes)
42 views42 pages

Slides3 Specifications PDF

This document summarizes a lecture on specifications and proofs in programming languages. It covers: 1) Specifications involve requires and ensures clauses to specify preconditions and postconditions of functions. Proofs involve showing functions satisfy their specifications using equational or evaluational reasoning. 2) Simple induction is introduced as a proof technique to prove properties of recursive functions when the size of arguments decreases with each recursive call. 3) An example proof by simple induction is given to show a recursive function f satisfies the specification that for all non-negative x, f(x) evaluates to 1. The base case and inductive step are proved using equational reasoning.

Uploaded by

252966576
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
42 views42 pages

Slides3 Specifications PDF

This document summarizes a lecture on specifications and proofs in programming languages. It covers: 1) Specifications involve requires and ensures clauses to specify preconditions and postconditions of functions. Proofs involve showing functions satisfy their specifications using equational or evaluational reasoning. 2) Simple induction is introduced as a proof technique to prove properties of recursive functions when the size of arguments decreases with each recursive call. 3) An example proof by simple induction is given to show a recursive function f satisfies the specification that for all non-negative x, f(x) evaluates to 1. The base case and inductive step are proved using equational reasoning.

Uploaded by

252966576
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 42

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?

You might also like