15-150 Fall 2020 Lecture 12
Stephen Brookes
MIT study says if you get more Z’s, you’ll get more A’s
Midterm 1 Tuesday, October 13 No class that day
15-150 Fall 2020 Lecture 12 Stephen Brookes Midterm 1 Tuesday, - - PowerPoint PPT Presentation
15-150 Fall 2020 Lecture 12 Stephen Brookes Midterm 1 Tuesday, October 13 No class that day MIT study says if you get more Zs, youll get more As Font of all knowledge? Australian researchers developed a typeface to help students
Stephen Brookes
MIT study says if you get more Z’s, you’ll get more A’s
Midterm 1 Tuesday, October 13 No class that day
Australian researchers developed a typeface to help students cramming for exams. A study found a small increase in the amount remembered. The font has a back slant and gaps. “If something is too easy to read, it doesn’t create a memory trace.”
Study for the Midterm!
We’re dealing with functions for problems that need careful specifications We may be informal (“positive integer x”) or more formal (“x > 0”) But whatever we do, MUST be clear
a list L of positive integers, and a constraint p : int list -> bool
and adds up to n?
a list L of positive integers, and a constraint p : int list -> bool
and adds up to n?
fun sublists L = foldr (fn (x, S) => S @ (map (fn A => x::A) S)) [ [ ] ] L
a list L of positive integers, and a constraint p : int list -> bool
and adds up to n?
fun sublists L = foldr (fn (x, S) => S @ (map (fn A => x::A) S)) [ [ ] ] L
val sublists = fn : 'a list -> 'a list list
val it = [[],[3],[2],[2,3],[1],[1,3],[1,2],[1,2,3]] : int list list
badchange : int * int list -> (int list -> bool) -> bool
A non-recursive function that returns a boolean
badchange : int * int list -> (int list -> bool) -> bool
A non-recursive function that returns a boolean
fun exists q = foldl (fn (x, t) => q x orelse t) false val sum = foldl (op +) 0
badchange : int * int list -> (int list -> bool) -> bool
A non-recursive function that returns a boolean
fun badchange (n, L) p = exists (fn A => sum A = n andalso p A) (sublists L)
fun exists q = foldl (fn (x, t) => q x orelse t) false val sum = foldl (op +) 0
badchange : int * int list -> (int list -> bool) -> bool
A non-recursive function that returns a boolean
fun badchange (n, L) p = exists (fn A => sum A = n andalso p A) (sublists L)
badchange satisfies the spec
fun exists q = foldl (fn (x, t) => q x orelse t) false val sum = foldl (op +) 0
badchange : int * int list -> (int list -> bool) -> bool
A non-recursive function that returns a boolean
fun badchange (n, L) p = exists (fn A => sum A = n andalso p A) (sublists L)
badchange satisfies the spec
fun exists q = foldl (fn (x, t) => q x orelse t) false val sum = foldl (op +) 0
but it’s inefficient!
========>* true
badchange (300, [1,2,3,...,24]) (fn _ => true)
(VERY SLOW!)
========>* true
brute force search
badchange (300, [1,2,3,...,24]) (fn _ => true)
(VERY SLOW!)
(224) ========>* true
brute force search
badchange (300, [1,2,3,...,24]) (fn _ => true)
(VERY SLOW!)
(224) … only the final sublist will work! ========>* true
brute force search
badchange (300, [1,2,3,...,24]) (fn _ => true)
(VERY SLOW!)
1 + 2 + … + 24 = 300
(224) … only the final sublist will work! ========>* true
brute force search
badchange (300, [1,2,3,...,24]) (fn _ => true)
(VERY SLOW!)
change : int * int list -> (int list -> bool) -> bool REQUIRES p is total, n≥0, L a list of positive integers ENSURES change (n, L) p = true if there is a sublist A of L with sum A = n and p A = true change (n, L) p = false, otherwise + must be faster! “can we make change for (n, L) that satisfies p?”
Avoid building the list of sublists
Deal with special cases first
For n > 0, L = x::R, use recursion... the spec suggests this might be feasible!
fun change (0, L) p = | change (n, [ ]) p = | change (n, x::R) p =
A recursive function that returns a boolean
p [ ] false if x <= n then change (n-x, R) (fn A => p(x::A))
change (n, R) p else change (n, R) p
change : int * int list -> (int list -> bool) -> bool
fun change (0, L) p = | change (n, [ ]) p = | change (n, x::R) p =
A recursive function that returns a boolean
change (300, [1,2,3,...,24]) (fn _ => true)
p [ ] false if x <= n then change (n-x, R) (fn A => p(x::A))
change (n, R) p else change (n, R) p
change : int * int list -> (int list -> bool) -> bool
fun change (0, L) p = | change (n, [ ]) p = | change (n, x::R) p =
A recursive function that returns a boolean
change (300, [1,2,3,...,24]) (fn _ => true) ⟹ true
(very fast)
p [ ] false if x <= n then change (n-x, R) (fn A => p(x::A))
change (n, R) p else change (n, R) p
change : int * int list -> (int list -> bool) -> bool
fun change (0, L) p = | change (n, [ ]) p = | change (n, x::R) p = p [ ] false if x <= n then case change (n-x, R) (fn A => p(x::A)) of true => true | false => change (n, R) p else change (n, R) p
(you’ll soon see why we mention this)
fun change (0, L) p = p [ ] | change (n, [ ]) p = false | change (n, x::R) p = if x <= n then change (n-x, R) (fn A => p(x::A))
change (n, R) p else change (n, R) p
CHECK that each clause fits this type: the LHS patterns match types int * int list and int list -> bool, and give bindings such that, assuming change : int * int list -> (int list -> bool) -> bool, the RHS expression gets type bool
change : int * int list -> (int list -> bool) -> bool
fun change (0, L) p = p [ ] | change (n, [ ]) p = false | change (n, x::R) p = if x <= n then change (n-x, R) (fn A => p(x::A))
change (n, R) p else change (n, R) p
REQUIRES p is total, n≥0, L a list of positive integers
CHECK if requires holds for (n, L, p) and change (n, L) p calls change (n’,L’) p’ and recursive call satisfies ensures
then requires holds for (n’, L’, p’) and original call satisfies ensures
ENSURES change (n, L) p = true iff there is a sublist A of L with sum A = n and p A = true
the function has the intended type(!)
inductive correctness proof
For all positive integer lists L, n ≥ 0, and total functions p : int list -> bool, change (n, L) p = true if there is a sublist A of L with sum A = n and p A = true change (n, L) p = false, otherwise
why?
change (10, [5,2,5]) (fn _ => true) = true change (10, [10,5,2,5]) (fn A => length(A)>1) = true change (10, [10,5,2]) (fn A => length(A)>1) = false change (210, [1,2,3,...,20]) (fn _ => true) ⟹* true (FAST!)
change : int * int list -> (int list -> bool) -> bool
Is there a sublist that adds to 300? change : int * int list -> (int list -> bool) -> bool
Is there a sublist that adds to 300?
val it = true (YES)
change : int * int list -> (int list -> bool) -> bool
Is there a sublist that adds to 300?
val it = true (YES)
What is it?
change : int * int list -> (int list -> bool) -> bool
Is there a sublist that adds to 300?
val it = true (YES)
What is it? You didn’t ask
change : int * int list -> (int list -> bool) -> bool
“it’s possible to make change...”
NEED a function that computes a suitable sublist, if there is one
“it’s possible to make change...”
PLAN a function that returns an (int list) option NEED a function that computes a suitable sublist, if there is one
“it’s possible to make change...”
mkchange : int * int list -> (int list -> bool) -> int list option REQUIRES p is total, n >= 0, L is a list of positive integers ENSURES mkchange (n, L) p = SOME A, where A is a sublist of L with sum A = n and p A = true, if there is one mkchange (n, L) p = NONE, otherwise A recursive function that returns an (int list) option
fun mkchange (0, L) p = | mkchange (n, [ ]) p = | mkchange (n, x::R) p = if p [ ] then SOME [ ] else NONE NONE if x <= n then case mkchange (n-x, R) (fn A => p(x::A)) of SOME A => SOME (x::A) | NONE => mkchange (n, R) p else mkchange (n, R) p
For all positive integer lists L, n ≥ 0, and total functions p : int list -> bool, mkchange (n, L) p = SOME A where A is a sublist of L with sum A = n and p A = true, if there is one mkchange (n, L) p = NONE, otherwise (similar to the proof of change)
mkchange : int * int list -> (int list -> bool) -> int list option change : int * int list -> (int list -> bool) -> bool
The definitions have similar control flow,
fun mkchange (0, L) p = if p [ ] then … else … | mkchange (n, [ ]) p = … | mkchange (n, x::R) p = if x <= n then case mkchange (n-x, R) (fn A => p(x::A)) of SOME A => … | NONE => mkchange (n, R) p else mkchange (n, R) p fun change (0, L) p = if p [ ] then … else … | change (n, [ ]) p = … | change (n, x::R) p = if x <= n then case change (n-x, R) (fn A => p(x::A)) of true => … | false => change (n, R) p else change (n, R) p
with pattern-matching based on the result type
change : int * int list -> (int list -> bool) -> bool mkchange : int * int list -> (int list -> bool) -> int list option
What if the boss decides he wants yet another type of answer?
What if the boss decides he wants yet another type of answer? How could we avoid having to keep re-doing the code design all over again?
use a type variable ’a for the answer type and use function parameters s and k to represent success and failure
s : int list -> ’a k : unit -> ’a
call s if you find a solution call k if you fail
(to be chosen later)
Choose the answer type to suit your task, later!
changer : int * int list -> (int list -> bool)
changer (n, L) p s k
: int list -> t : unit -> t
: t
this recipe works for any type t
the “answer” type, to be chosen later
pattern match on values of type ’a
case of (* what — we don’t know the type *)
changer (n-x, R) p’ s’ k’
case change (n-x, R) p’ of true => … | false => … case mkchange (n-x, R) p’ of SOME A => … | NONE => …
change : int * int list -> … -> bool mkchange : int * int list -> … -> (int list) option
changer : int * int list -> … -> 'a
design the function to
is a success continuation s : int list -> t
“there will be an answer, let it be s [1,2,3]”
k : unit -> t is a failure continuation
“I don’t have an answer, so try using k( )”
changer : int * int list -> (int list -> bool)
REQUIRES n>=0, L a list of positive integers, p total ENSURES changer (n, L) p s k = s A where A is a sublist of L such that sum A = n and p A = true, if there is one changer (n, L) p s k = k ( )
success, call s with a value failure, call k
fun changer (0, L) p s k = | changer (n, [ ]) p s k = | changer (n, x::R) p s k = if p [ ] then s [ ] else k( ) k( ) if x <= n then changer (n-x, R) (fn A => p(x::A)) (fn A => s(x::A)) (fn ( ) => changer (n, R) p s k) else changer (n, R) p s k
fun changer (0, L) p s k = if p [ ] then s [ ] else k( ) | changer (n, [ ]) p s k = k( ) | changer (n, x::R) p s k = if x <= n then changer (n-x, R) (fn A => p(x::A)) (fn A => s(x::A)) (fn ( ) => changer (n, R) p s k) else changer (n, R) p s k
check type and spec
(fn _ => true) SOME (fn () => NONE); val it = SOME [1,1,1,1,1,1,2,4] : int list option
(fn A => length A < 4) SOME (fn () => NONE); val it = SOME [2,4,6] : int list option
For all lists L of positive integers, n ≥ 0, total functions p : int list -> bool, all types t and all s : int list -> t, k : unit -> t changer (n, L) p s k = s A where A is a sublist of L with sum A = n and p A = true, if there is one changer (n, L) p s k = k( ), otherwise (similar to the proofs for change, mkchange…)
“For all types t, all total functions p, …yada yada yada”
fun change (n, L) p =
fun mkchange (n, L) p =
changer : int * int list -> (int list -> bool)
change : int * int list -> (int list -> bool) -> bool mkchange : int * int list -> (int list -> bool) -> int list option
changer (n, L) p (fn _ => true) (fn ( ) => false)
changer (n, L) p SOME (fn ( ) => NONE)
manager decides to change the spec again, e.g. by giving an extra penny
fun generous_change (n, L) p = changer (n, L) p (fn A => 1::A) (fn ( ) => [1])
val it = [1,5,5,2] : int list
generous_change : int * int list -> (int list -> bool) -> int list
recursive calls… so you can use inductive hypothesis that recursive calls work correctly
can be used to solve a problem that we initially solved directly
there is a continuation-style function that “does the same job”…
A function of type t1 -> t2 can be applied to an argument of type t1, and returns a value of type t2. A direct-style function evaluates its argument, returns a result.
A function of type t1 -> t2 can be applied to an argument of type t1, and returns a value of type t2. A direct-style function evaluates its argument, returns a result.
“here’s my number...”
A continuation-style function of type t1 -> (t2 -> ’a) -> ’a can be applied to an argument of type t1 and a continuation of type t2 -> a, where a is a type of “answers”. Instead of “returning” a result value of type t2, it passes that value to its continuation, to get an “answer”.
A continuation-style function of type t1 -> (t2 -> ’a) -> ’a can be applied to an argument of type t1 and a continuation of type t2 -> a, where a is a type of “answers”. Instead of “returning” a result value of type t2, it passes that value to its continuation, to get an “answer”.
“here’s my number, give me an answer”
“the rest of a computation” or “what to do next”
with a value (“the result so far”) and computes a final value (an “answer”)
can be turned into a cps function definition
intermediate values explicit
and polymorphic
fun fact n = if n=0 then 1 else n * fact(n-1) (* fact : int -> int *) fun fact_cps n k = if n=0 then k 1 else fact_cps(n-1)(fn x => k(n * x)) (* fact_cps: int -> (int -> ’a) -> ’a *)
direct continuation style style
not tail recursive tail recursive
| prod (x::L) = x * prod L; val prod = fn : int list -> int
| prod_cps (x::L) k = prod_cps L (fn y => k(x*y)); val prod_cps = fn : int list -> (int -> 'a) -> 'a
prod_cps is the continuation-style version of prod
| prod (x::L) = x * prod L; val prod = fn : int list -> int
| Prod (x::L) k = x * Prod L k; val Prod = fn : int list -> (int -> int) -> int
Prod is not the cps version of prod! Its type is also weirdly specific (an instance of the expected type, with int for ’a)
| split [x] = ([x], [ ]) | split (x::y::L) = let val (A, B) = split L in (x::A, y::B) end; val split = fn : 'a list -> 'a list * 'a list
| split_cps [x] k = k ([x], [ ]) | split_cps (x::y::L) k = split_cps L (fn (A, B) => k(x::A, y::B)); val split_cps = fn : 'a list -> ('a list * 'a list -> 'b) -> 'b
split_cps is the continuation-style version of split
“does the same job”
F : t1 -> t2 F_cps : t1 -> (t2 -> ’a) -> ’a
Theorem: For all types t, values x : t1, k : t2 -> t, F_cps x k = k (F x).
Prove Theorems like this for (i) fact and fact_cps (ii) prod and prod_cps (iii) split and split_cps
it calls a continuation instead
typically less polymorphic, you got it wrong.
The cps version of f is NOT just fun f_cps x k = k(f x)
and hand-in instructions
October 13
from a dead end, to an earlier choice point, and explore further alternatives
call it , maybe!
October 13