SLIDE 1 15-150 Fall 2020
Stephen Brookes Lecture 3
Patterns and specifications
SLIDE 2 Patterns and specifications
SLIDE 3 Patterns and specifications
SLIDE 4 Advice
- After class, study slides and lecture notes.
- Start homework early, plan to finish on time.
- Don’t use piazza as a first resort,
- r close to a handin deadline.
- Ask for help only after you’ve studied, and tried.
- Think before you write.
SLIDE 5 Today
- A brief remark about equality types
- Patterns and how to use them
- Specifying program behavior
evaluation and equivalence
SLIDE 6 equality in ML
e1 = e2
- Only for expressions whose type is an equality type
- Equality types are built from
int, bool, - * -, and -list
SLIDE 7 equality in ML
e1 = e2
- Only for expressions whose type is an equality type
- Equality types are built from
int, bool, - * -, and -list e.g. int list int * bool (int * bool) list
SLIDE 8 equality in ML
e1 = e2
- Only for expressions whose type is an equality type
- Equality types are built from
int, bool, - * -, and -list
SLIDE 9 equality in ML
e1 = e2
- Only for expressions whose type is an equality type
- Equality types are built from
int, bool, - * -, and -list but NOT real or ->
SLIDE 10 equality in ML
e1 = e2
- Only for expressions whose type is an equality type
- Equality types are built from
int, bool, - * -, and -list
SLIDE 11 equality in ML
e1 = e2
- Only for expressions whose type is an equality type
- Equality types are built from
int, bool, - * -, and -list
val it = true : bool
SLIDE 12 equality in ML
e1 = e2
- Only for expressions whose type is an equality type
- Equality types are built from
int, bool, - * -, and -list
val it = true : bool
val it = true : bool
SLIDE 13 equality in ML
e1 = e2
- Only for expressions whose type is an equality type
- Equality types are built from
int, bool, - * -, and -list
val it = true : bool
val it = true : bool
- (fn x => x+x) = (fn y => 2*y);
Error: operator and operand don't agree [equality type required]
SLIDE 14 equality in ML
e1 = e2
- Only for expressions whose type is an equality type
- Equality types are built from
int, bool, - * -, and -list
val it = true : bool
val it = true : bool
SLIDE 15 equality in ML
e1 = e2
- Only for expressions whose type is an equality type
- Equality types are built from
int, bool, - * -, and -list
val it = true : bool
val it = true : bool
val equal = fn - : ”a * ”a -> bool
SLIDE 16 equality in ML
e1 = e2
- Only for expressions whose type is an equality type
- Equality types are built from
int, bool, - * -, and -list
val it = true : bool
val it = true : bool
val equal = fn - : ”a * ”a -> bool
type variable ’’a stands for any equality type
SLIDE 17 notation overload
- ML syntax uses = for several purposes
fn x => e fun f(x) = e val x = 2 val even = fn x => (x mod 2 = 0) fun leq(x, y) = (x <= y) fun geq(x, y) = (x >= y) We also use = in math for “equality”
SLIDE 18 patterns
- ML includes patterns, for matching with values
- Matching p to value v either fails,
- r succeeds and binds names to values
p ::= _ | x | n | true | false | (p1, …, pk) | p1::p2 | [p1, …, pk] (can attach type : t if desired) Syntactic restriction: each x occurs at most once in p
SLIDE 19 pattern matching
- _ always matches v
- x always matches v (and binds x to v)
- n only matches n, true only matches true
- (p1, p2) matches (v1, v2)
if p1 matches v1 and p2 matches v2 (combines the bindings)
- nil only matches the empty list
- p1::p2 matches non-empty lists v1::v2 for
which p1 matches v1 and p2 matches v2 (combines the bindings) with values
no ambiguity, because of variable constraint no ambiguity, because of variable constraint
SLIDE 20 utility
- When a value of a given type is expected,
code can use patterns specific to that type integers… booleans… 3-tuples… lists… 0, 42, …, x, x:int… true, false, x, x:bool… (x, y, z), (0, true, _), … nil, x::L, [x, y, z], … (x:int, L:int list) x::(y::L)
SLIDE 21 syntax
d ::= val p : t = e | fun f (p:t1):t2 = e | fun f (p1 : t) : t’= e1 | f p2 = e2 e ::= fn (p:t1):t2 => e2 | case e0 : t of p1 => e1 | p2 => e2
- ptional : type annotations
fun, fn and case syntax allows k clauses declarations expressions using patterns
(all clauses must have the same type)
et cetera et cetera
SLIDE 22 functions using patterns
fun f p1 = e1 | … | f pk = ek fn p1 => e1 | … | pk => ek
tries matching p1 to v, then p2,…, pk until the first match
f v
SLIDE 23 functions using patterns
fun len [ ] = 0 | len (_::L) = 1 + len L fun f p1 = e1 | … | f pk = ek fn p1 => e1 | … | pk => ek
tries matching p1 to v, then p2,…, pk until the first match
f v
SLIDE 24 functions using patterns
fun len [ ] = 0 | len (_::L) = 1 + len L len [3]
[3] doesn’t match pattern [ ] [3] matches pattern _::L, binding L to [ ]
= 1 + len [ ] = 1 + 0 fun f p1 = e1 | … | f pk = ek fn p1 => e1 | … | pk => ek
tries matching p1 to v, then p2,…, pk until the first match
f v
SLIDE 25 examples
fun fact 0 = 1 | fact 1 = 1 | fact n = n * fact (n-1) fun length [ ] = 0 | length (_::L) = 1 + length L fn [ ] => true | _ => false
length : ’a list -> int
fact : int -> int
: ’a list -> bool
val x::L = [1,2,3]
binds x to 1, L to [2,3]
using patterns
SLIDE 26 rules of thumb
- Pay attention to clause order
- Use exhaustive patterns
- Avoid overlapping patterns (unless it’s safe)
- Can use _ when the binding is irrelevant
fun f(p1:t):t’ = e1 | f(p2) = e2 | f(p3) = e3 case e:t of p1 => e1 | p2 => e2 | p3 => e3
Tries p1, then p2, then p3 Every value of type t matches at least one of p1, p2, p3 Every value of type t matches at most one of p1, p2, p3 Or, if v matches pi and pj make sure ei and ej will be equal First match “wins” Sometimes it’s convenient to use _ in the final clause
SLIDE 27 Constant patterns can only be used to match values
fun f(0) = 1 | f(1) = 1 | f(n) = f(n-1) + f(n-2) case e of true => e1 | false => e2
int bool
SLIDE 28 Constant patterns can only be used to match values
fun f(0) = 1 | f(1) = 1 | f(n) = f(n-1) + f(n-2) case e of true => e1 | false => e2 if e then e1 else e2
int bool
SLIDE 29
Using patterns
fun divmod (x:int, y:int): int*int = (x div y, x mod y) divmod : int * int -> int * int fun check (m:int, n:int): bool = let val (q, r) = divmod (m, n) in (q * n + r = m) end
SLIDE 30
Using patterns
fun divmod (x:int, y:int): int*int = (x div y, x mod y) What does this function do? divmod : int * int -> int * int fun check (m:int, n:int): bool = let val (q, r) = divmod (m, n) in (q * n + r = m) end
SLIDE 31
decimal : int -> int list
fun decimal (n:int) : int list = if n < 10 then [n] else (n mod 10) :: decimal (n div 10)
SLIDE 32
decimal : int -> int list
fun decimal (n:int) : int list = if n < 10 then [n] else (n mod 10) :: decimal (n div 10) What does this function do?
SLIDE 33
decimal : int -> int list
decimal 42 = [2,4] decimal 0 = [0] fun decimal (n:int) : int list = if n < 10 then [n] else (n mod 10) :: decimal (n div 10) What does this function do?
SLIDE 34 eval : int list -> int
This definition uses list patterns
- [ ] matches (only) the empty list
- d::L matches a non-empty list,
binds d to head of the list, L to its tail fun eval ([ ]:int list) : int = 0 | eval (d::L) = d + 10 * (eval L) eval [2,4] ⟹* 2 + 10 * (eval [4]) ⟹* 42
SLIDE 35 eval : int list -> int
This definition uses list patterns
- [ ] matches (only) the empty list
- d::L matches a non-empty list,
binds d to head of the list, L to its tail fun eval ([ ]:int list) : int = 0 | eval (d::L) = d + 10 * (eval L) eval [2,4] ⟹* 2 + 10 * (eval [4]) ⟹* 42 What does this function do?
SLIDE 36
log : int -> int
fun log (x:int) : int = if x = 1 then 0 else 1 + log (x div 2) log 3 = ???
SLIDE 37 log : int -> int
- Q: How can we describe this function?
- A: Specify its applicative behavior…
fun log (x:int) : int = if x = 1 then 0 else 1 + log (x div 2) log 3 = ???
SLIDE 38 log : int -> int
- Q: How can we describe this function?
- A: Specify its applicative behavior…
fun log (x:int) : int = if x = 1 then 0 else 1 + log (x div 2)
- For what argument values does it terminate?
log 3 = ???
SLIDE 39 log : int -> int
- Q: How can we describe this function?
- A: Specify its applicative behavior…
fun log (x:int) : int = if x = 1 then 0 else 1 + log (x div 2)
- For what argument values does it terminate?
- How does the output relate to the input?
log 3 = ???
SLIDE 40 Specifications
(showing argument type and result type)
(about argument value)
(about result value, when assumption holds) For each function definition we specify:
SLIDE 41 Format
fun log (x:int) : int = if x=1 then 0 else 1 + log (x div 2) (* TYPE log : int -> int *) (* REQUIRES … x … *) (* ENSURES … log x …. *) type assumption guarantee
Any ideas?
For all values x : int satisfying the assumption, log x : int and its value satisfies the guarantee
SLIDE 42
log spec
fun log (x:int) : int = if x=1 then 0 else 1 + log (x div 2) (* TYPE log : int -> int *) (* REQUIRES x > 0 *) (* ENSURES log x = the integer k ≥ 0 *) (* such that 2k ≤ x < 2k+1 *)
SLIDE 43 log spec
fun log (x:int) : int = if x=1 then 0 else 1 + log (x div 2) (* TYPE log : int -> int *) (* REQUIRES x > 0 *) (* ENSURES log x = the integer k ≥ 0 *) (* such that 2k ≤ x < 2k+1 *)
For all integers x such that x>0, the value of log x is an integer k such that 2k ≤ x < 2k+1
SLIDE 44 notes
- Can use ⟹* or = in specs
- Use math and logic accurately!
- A function can have several specs…
different assumptions may lead to different guarantee
SLIDE 45
another log spec
fun log (x:int) : int = if x=1 then 0 else 1 + log (x div 2) (* log : int -> int *) (* REQUIRES x is a power of 2 *) (* ENSURES log x = the integer k *) (* such that 2k = x *)
SLIDE 46
another log spec
fun log (x:int) : int = if x=1 then 0 else 1 + log (x div 2) (* log : int -> int *) (* REQUIRES x is a power of 2 *) (* ENSURES log x = the integer k *) (* such that 2k = x *) (a weaker spec … why?)
SLIDE 47
another log spec
fun log (x:int) : int = if x=1 then 0 else 1 + log (x div 2) (* log : int -> int *) (* REQUIRES x is a power of 2 *) (* ENSURES log x = the integer k *) (* such that 2k = x *) (a weaker spec … why?) (it’s actually implied by the previous spec)
SLIDE 48 decimal spec
TYPE decimal : int -> int list REQUIRES n ≥ 0 ENSURES decimal n = the decimal digit list for n fun decimal (n:int) : int list = if n<10 then [n] else (n mod 10) :: decimal (n div 10) (with least significant digit first)
decimal 42 = [2,4]
SLIDE 49
eval spec
TYPE eval : int list -> int REQUIRES R = the decimal digit list for n ENSURES eval R = n fun eval ([ ] : int list) : int = 0 | eval (d::L) = d + 10 * (eval L)
SLIDE 50 connection
- eval and decimal are designed to fit together
- They satisfy a combined spec
TYPE decimal : int -> int list eval : int list -> int REQUIRES n ≥ 0 ENSURES eval(decimal n) = n
SLIDE 51 connection
- eval and decimal are designed to fit together
- They satisfy a combined spec
TYPE decimal : int -> int list eval : int list -> int REQUIRES n ≥ 0 ENSURES eval(decimal n) = n NOTE: this spec tells us that decimal n evaluates to a value, for n ≥ 0
SLIDE 52 Evaluation
- Expression evaluation produces a value if it terminates
- e ⟹k e’ e evaluates to e’ in k steps
- e ⟹* v e evaluates to v in finitely many steps
- Declarations produce value bindings
- d ⟹* x1:v1, ..., xk:vk
- Matching a pattern to a value
either succeeds with bindings, or fails
- match(p, v) ⟹* x1:v1, ..., xk:vk | fail
TYPE SAFE
SLIDE 53 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 [ x:2 ] (x + x) is 2 + 2 [ x:2 ] (fn y => x + y) is fn y => 2 + y
(substitute for free occurrences, only)
[ x:2 ] (fn x => x + x) fn x => x + x is
SLIDE 54 rules
- For each syntactic construct we give
evaluation rules for ⟹
- showing order-of-evaluation
- We derive evaluation laws for ⟹
- how expressions evaluate
- what is the value, if it terminates
- We can also count number of steps ⟹
(n) *
(“one-step-to”) (“many-steps-to”) (“takes n steps to”)
(mostly for sequential evaluation)
SLIDE 55 addition rules
e1 ⟹ e1’
e1+e2 evaluates from left-to-right
e1 + e2 ⟹ e1’ + e2 e2 ⟹ e2’ v1 + e2 ⟹ v1 + e2’ v1 + v2 ⟹ v ei, vi : int
where v = v1 + v2
if e1 steps to e1’, then e1+e2 steps to e1’+e2 v1+v2 steps to the numeral for v1+v2
SLIDE 56
addition law
If e1 ⟹* v1 and e2 ⟹* v2 e1 + e2 ⟹* v then and v = v1 + v2 (follows from the rules) e1 + e2 ⟹* v1 + e2 ⟹* v1 + v2 ⟹ v
SLIDE 57 addition law
If e1 ⟹* v1 and e2 ⟹* v2 e1 + e2 ⟹* v then and v = v1 + v2 (2+2) + (3+3) ⟹ 4 + (3+3)
⟹ 4 + 6 ⟹ 10
(follows from the rules) e1 + e2 ⟹* v1 + e2 ⟹* v1 + v2 ⟹ v
SLIDE 58 addition law
If e1 ⟹* v1 and e2 ⟹* v2 e1 + e2 ⟹* v then and v = v1 + v2 (2+2) + (3+3) ⟹ 4 + (3+3)
⟹ 4 + 6 ⟹ 10
(2+2) + (3+3) ⟹* 10 (follows from the rules) e1 + e2 ⟹* v1 + e2 ⟹* v1 + v2 ⟹ v
SLIDE 59 addition law
If e1 ⟹* v1 and e2 ⟹* v2 e1 + e2 ⟹* v then and v = v1 + v2 (2+2) + (3+3) ⟹ 4 + (3+3)
⟹ 4 + 6 ⟹ 10
(2+2) + (3+3) ⟹* 10 (2+2) + (3+3) ⟹(3) 10 (follows from the rules) e1 + e2 ⟹* v1 + e2 ⟹* v1 + v2 ⟹ v
SLIDE 60
addition law
If e1 + e2 ⟹* v there must be v1 : int and v2 : int such that and v = v1 + v2 (also follows from the rules) e1 ⟹* v1 and e2 ⟹* v2 e1 + e2 ⟹* v1 + e2 ⟹* v1 + v2 ⟹ v and the evaluation looks like (this shows the order of evaluation clearly!)
SLIDE 61 application rules
“a function always evaluates its argument”
e1 ⟹ e1’ e1 e2 ⟹ e1’ e2 e2 ⟹ e2’ (fn x => e) e2 ⟹ (fn x => e) e2’ (fn x => e) v ⟹ [ x:v ] e
(this rule only applicable when function and argument have been evaluated to values) (call-by-value)
e1 e2 evaluates e1 to a function, evaluates e2 to a value, substitutes the value into the function body, then evaluates the body
SLIDE 62
application law
If e1 ⟹* (fn x => e) and e2 ⟹* v then e1 e2 ⟹* [x:v]e (follows from the rules)
SLIDE 63 application law
If e1 ⟹* (fn x => e) and e2 ⟹* v then e1 e2 ⟹* [x:v]e (follows from the rules)
this expression may need further evaluation
SLIDE 64
application law
If e1 e2 ⟹* v there must be values (fn x => e) : t1->t2 and v2 : t1 such that e1 ⟹* (fn x => e) and e2 ⟹* v2 and e1 e2 ⟹* (fn x => e) e2 ⟹* (fn x => e) v2 ⟹ [x:v2]e ⟹* v (also follows from the rules)
SLIDE 65 More rules
- div and mod evaluate from left to right
- List expressions
[e1,…,en], e1::e2, and e1@e2 all evaluate from left to right
- Tuple expressions (e1,…,en)
can be evaluated from left to right,
- r (as we’ll see later) in parallel.
SLIDE 66 More rules
- div and mod evaluate from left to right
- List expressions
[e1,…,en], e1::e2, and e1@e2 all evaluate from left to right
- Tuple expressions (e1,…,en)
can be evaluated from left to right,
- r (as we’ll see later) in parallel.
SLIDE 67
Declaration rule
In the scope of fun f(p) = e, f ⟹ (fn p => e)
SLIDE 68
Declaration rule
In the scope of fun f(p) = e, f ⟹ (fn p => e) fun divmod(x, y) = (x div y, x mod y)
SLIDE 69
Declaration rule
In the scope of fun f(p) = e, f ⟹ (fn p => e) fun divmod(x, y) = (x div y, x mod y) divmod (3,2)
SLIDE 70
Declaration rule
In the scope of fun f(p) = e, f ⟹ (fn p => e) fun divmod(x, y) = (x div y, x mod y) divmod (3,2) ⟹ (fn(x, y) => (x div y, x mod y)) (3,2)
SLIDE 71
Declaration rule
In the scope of fun f(p) = e, f ⟹ (fn p => e) fun divmod(x, y) = (x div y, x mod y) divmod (3,2) ⟹ (fn(x, y) => (x div y, x mod y)) (3,2) ⟹ (3 div 2, 3 mod 2)
SLIDE 72
Declaration rule
In the scope of fun f(p) = e, f ⟹ (fn p => e) fun divmod(x, y) = (x div y, x mod y) divmod (3,2) ⟹ (fn(x, y) => (x div y, x mod y)) (3,2) ⟹ (3 div 2, 3 mod 2) ⟹ (1, 3 mod 2)
SLIDE 73
Declaration rule
In the scope of fun f(p) = e, f ⟹ (fn p => e) fun divmod(x, y) = (x div y, x mod y) divmod (3,2) ⟹ (fn(x, y) => (x div y, x mod y)) (3,2) ⟹ (3 div 2, 3 mod 2) ⟹ (1, 3 mod 2) ⟹ (1, 1)
SLIDE 74
example
fun silly x = silly x; (fn y => 0) (silly 42) doesn’t terminate
SLIDE 75
example
fun silly x = silly x; (fn y => 0) (silly 42) doesn’t terminate (fn y => 0) (silly 42)
SLIDE 76
example
fun silly x = silly x; (fn y => 0) (silly 42) doesn’t terminate (fn y => 0) (silly 42) ⟹
SLIDE 77
example
fun silly x = silly x; (fn y => 0) (silly 42) doesn’t terminate (fn y => 0) (silly 42) (fn y => 0) ((fn x => silly x) 42) ⟹
SLIDE 78
example
fun silly x = silly x; (fn y => 0) (silly 42) doesn’t terminate (fn y => 0) (silly 42) (fn y => 0) ((fn x => silly x) 42) ⟹ ⟹
SLIDE 79
example
fun silly x = silly x; (fn y => 0) (silly 42) doesn’t terminate (fn y => 0) (silly 42) (fn y => 0) ((fn x => silly x) 42) (fn y => 0) (silly 42) ⟹ ⟹
SLIDE 80 example
fun silly x = silly x; (fn y => 0) (silly 42) doesn’t terminate (fn y => 0) (silly 42) (fn y => 0) ((fn x => silly x) 42) (fn y => 0) (silly 42) ⟹ ⟹
ad infinitum
SLIDE 81 example
fun silly x = silly x; (fn y => 0) (silly 42) doesn’t terminate (fn y => 0) (silly 42) (fn y => 0) ((fn x => silly x) 42) (fn y => 0) (silly 42) ⟹ ⟹
ad infinitum
functions evaluate their argument
SLIDE 82 Comments
- Using ⟹ we can talk about evaluation order
and the number of steps
- But we may want to ignore such details...
For all expressions e1, e2 : int and all values v:int, if e1 + e2 ⟹* v then e2 + e1 ⟹* v Here we only care about the value e1 + e2 = e2 + e1 For all expressions e1, e2 : int,
the same, more succinctly
SLIDE 83 Equivalence
- For each type t there is a mathematical notion of
equivalence (or equality) =t for values of type t
(it’s all about the value…)
- Expressions of type t are equivalent iff
they evaluate to equivalent values, or both diverge
SLIDE 84 Equivalence
- For each type t there is a mathematical notion of
equivalence (or equality) =t for values of type t v1 =int v2 ⇔ v1 = v2
(as expected!) (it’s all about the value…)
- Expressions of type t are equivalent iff
they evaluate to equivalent values, or both diverge
SLIDE 85 Equivalence
- For each type t there is a mathematical notion of
equivalence (or equality) =t for values of type t
(it’s all about the value…)
- Expressions of type t are equivalent iff
they evaluate to equivalent values, or both diverge
SLIDE 86 Equivalence
- For each type t there is a mathematical notion of
equivalence (or equality) =t for values of type t f1 =int->int f2 ⇔
∀v1,v2:int. (v1 =int v2 implies f1 v1 =int f2 v2)
(it’s all about the value…)
- Expressions of type t are equivalent iff
they evaluate to equivalent values, or both diverge
SLIDE 87 Equivalence
- For each type t there is a mathematical notion of
equivalence (or equality) =t for values of type t f1 =int->int f2 ⇔
∀v1,v2:int. (v1 =int v2 implies f1 v1 =int f2 v2)
(equivalent functions map equal arguments to equal results) (it’s all about the value…)
- Expressions of type t are equivalent iff
they evaluate to equivalent values, or both diverge
SLIDE 88 Equations
e1 + e2 =int e2 + e1 e1 + (e2 + e3) =int (e1 + e2) + e3 e + 0 =int e if true then e1 else e2 =t e1 if false then e1 else e2 =t e2 21 + 21 =int 42 (0 < 1) =bool true
SLIDE 89 Equations
(fn x => e) v = [x:v]e In the scope of f =t1->t2 (fn x => e) the equation fun f(x:t1):t2 = e holds
the argument is a value
SLIDE 90 Equations
(fn x => e) v = [x:v]e In the scope of f =t1->t2 (fn x => e) the equation fun f(x:t1):t2 = e holds
SLIDE 91 Equations
(fn x => e) v = [x:v]e In the scope of f =t1->t2 (fn x => e) the equation fun f(x:t1):t2 = e holds let val x = v in e end = [x:v]e
SLIDE 92 Compositionality
- Substitution of equals
- If e1 = e2 and e1’ = e2’
then (e1 e1’) = (e2 e2’)
then (e1 + e1’) = (e2 + e2’) and so on
SLIDE 93
Key facts
evaluation is consistent with equivalence
SLIDE 94 Key facts
- e : t and e ⟹* v implies v : t and e =t v
- e ⟹* v implies (fn x => E) e = [x:v] E
evaluation is consistent with equivalence
SLIDE 95 Key facts
- e : t and e ⟹* v implies v : t and e =t v
- e ⟹* v implies (fn x => E) e = [x:v] E
evaluation is consistent with equivalence
f 3 ⟹* 0 f 3 =int 0
Standard ML of New Jersey
fun f(x:int) = 0; …
SLIDE 96 Summary
- Patterns allow elegant function design
patterns match subset of values function tries its clauses in order so be careful about clause order
- Specifications can serve as clear documentation
TYPE + REQUIRES and ENSURES equality and evaluation
SLIDE 97
but usually cannot cover all cases
- How to prove that a function
meets its specification...
- Proof methods use induction
Coming soon
SLIDE 98
but usually cannot cover all cases
- How to prove that a function
meets its specification...
- Proof methods use induction
Coming soon