15-150 Fall 2020 Lecture 12 Stephen Brookes Midterm 1 Tuesday, - - PowerPoint PPT Presentation

15 150 fall 2020 lecture 12
SMART_READER_LITE
LIVE PREVIEW

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


slide-1
SLIDE 1

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

slide-2
SLIDE 2

Font of all knowledge?

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!

slide-3
SLIDE 3

and now…

  • Generalizing the subset-sum problem
  • Developing specs and code together
  • A lesson in program design

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

slide-4
SLIDE 4

making change

  • Given a non-negative integer n,

a list L of positive integers, and a constraint p : int list -> bool

  • Is there a sublist of L that satisfies p

and adds up to n?

slide-5
SLIDE 5

making change

  • Given a non-negative integer n,

a list L of positive integers, and a constraint p : int list -> bool

  • Is there a sublist of L that satisfies p

and adds up to n?

fun sublists L = foldr (fn (x, S) => S @ (map (fn A => x::A) S)) [ [ ] ] L

slide-6
SLIDE 6

making change

  • Given a non-negative integer n,

a list L of positive integers, and a constraint p : int list -> bool

  • Is there a sublist of L that satisfies p

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

  • sublists [1,2,3];

val it = [[],[3],[2],[2,3],[1],[1,3],[1,2],[1,2,3]] : int list list

slide-7
SLIDE 7

badchange : int * int list -> (int list -> bool) -> bool

A non-recursive function that returns a boolean

slide-8
SLIDE 8

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

slide-9
SLIDE 9

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

slide-10
SLIDE 10

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

slide-11
SLIDE 11

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!

slide-12
SLIDE 12

========>* true

critique

badchange (300, [1,2,3,...,24]) (fn _ => true)

(VERY SLOW!)

slide-13
SLIDE 13

========>* true

critique

brute force search

  • generates the list of all sublists
  • and tests them, sequentially

badchange (300, [1,2,3,...,24]) (fn _ => true)

(VERY SLOW!)

slide-14
SLIDE 14

(224) ========>* true

critique

brute force search

  • generates the list of all sublists
  • and tests them, sequentially

badchange (300, [1,2,3,...,24]) (fn _ => true)

(VERY SLOW!)

slide-15
SLIDE 15

(224) … only the final sublist will work! ========>* true

critique

brute force search

  • generates the list of all sublists
  • and tests them, sequentially

badchange (300, [1,2,3,...,24]) (fn _ => true)

(VERY SLOW!)

slide-16
SLIDE 16

1 + 2 + … + 24 = 300

(224) … only the final sublist will work! ========>* true

critique

brute force search

  • generates the list of all sublists
  • and tests them, sequentially

badchange (300, [1,2,3,...,24]) (fn _ => true)

(VERY SLOW!)

slide-17
SLIDE 17

specification

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?”

slide-18
SLIDE 18

a better strategy

Avoid building the list of sublists

  • only call p on sublists with the correct sum

Deal with special cases first

  • n = 0
  • n > 0, L = [ ]

For n > 0, L = x::R, use recursion... the spec suggests this might be feasible!

slide-19
SLIDE 19

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))

  • relse

change (n, R) p else change (n, R) p

change : int * int list -> (int list -> bool) -> bool

slide-20
SLIDE 20

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))

  • relse

change (n, R) p else change (n, R) p

change : int * int list -> (int list -> bool) -> bool

slide-21
SLIDE 21

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))

  • relse

change (n, R) p else change (n, R) p

change : int * int list -> (int list -> bool) -> bool

slide-22
SLIDE 22

equivalently

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)

slide-23
SLIDE 23

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))

  • relse

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

type check

change : int * int list -> (int list -> bool) -> bool

slide-24
SLIDE 24

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))

  • relse

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

spec check

ENSURES change (n, L) p = true iff there is a sublist A of L with sum A = n and p A = true

slide-25
SLIDE 25

benefits

  • The type check shows that

the function has the intended type(!)

  • Use type check to guide the code design
  • ML will give same type (or a more general one)
  • The spec check is basically a sketch of an

inductive correctness proof

  • Use spec check to guide the code design
  • Will then be easy to give a fully detailed proof
  • f correctness
slide-26
SLIDE 26

correctness

  • PROOF: induction on L

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?

slide-27
SLIDE 27

examples

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!)

slide-28
SLIDE 28

the right question?

change : int * int list -> (int list -> bool) -> bool

slide-29
SLIDE 29

the right question?

Is there a sublist that adds to 300? change : int * int list -> (int list -> bool) -> bool

slide-30
SLIDE 30

the right question?

Is there a sublist that adds to 300?

val it = true (YES)

change : int * int list -> (int list -> bool) -> bool

slide-31
SLIDE 31

the right question?

Is there a sublist that adds to 300?

val it = true (YES)

What is it?

change : int * int list -> (int list -> bool) -> bool

slide-32
SLIDE 32

the right question?

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

slide-33
SLIDE 33

boolean blindness

  • A truth value only gives a bit of information
  • From the context, we know true (yes) means

“it’s possible to make change...”

  • But what if we want more information?
  • “a list that works, if there is one”
slide-34
SLIDE 34

NEED a function that computes a suitable sublist, if there is one

boolean blindness

  • A truth value only gives a bit of information
  • From the context, we know true (yes) means

“it’s possible to make change...”

  • But what if we want more information?
  • “a list that works, if there is one”
slide-35
SLIDE 35

PLAN a function that returns an (int list) option NEED a function that computes a suitable sublist, if there is one

boolean blindness

  • A truth value only gives a bit of information
  • From the context, we know true (yes) means

“it’s possible to make change...”

  • But what if we want more information?
  • “a list that works, if there is one”
slide-36
SLIDE 36

mkchange

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

slide-37
SLIDE 37

mkchange

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

slide-38
SLIDE 38

correctness?

  • PROOF: induction on L

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)

slide-39
SLIDE 39

assessment

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

slide-40
SLIDE 40

problem

slide-41
SLIDE 41

problem

slide-42
SLIDE 42

problem

What if the boss decides he wants yet another type of answer?

slide-43
SLIDE 43

problem

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?

slide-44
SLIDE 44
  • Design a very flexible function
  • Instead of returning a bool or int list option,

use a type variable ’a for the answer type and use function parameters s and k to represent success and failure

go polymorphic...

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!

slide-45
SLIDE 45

changer : int * int list -> (int list -> bool)

  • > (int list -> 'a) -> (unit -> 'a) -> 'a

more flexible

changer (n, L) p s k

: int list -> t : unit -> t

: t

this recipe works for any type t

  • f “answers”
slide-46
SLIDE 46

warning

  • We’re using a type variable ’a to stand for

the “answer” type, to be chosen later

  • When we design the code, we cannot

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

slide-47
SLIDE 47

no problem

  • Instead of returning a value,

design the function to

  • either call the success continuation with a value
  • or call the failure continuation
  • Just remember: never return!
  • Spoiler alert: This is “continuation-passing style”
slide-48
SLIDE 48

success

is a success continuation s : int list -> t

“there will be an answer, let it be s [1,2,3]”

slide-49
SLIDE 49

failure

k : unit -> t is a failure continuation

“I don’t have an answer, so try using k( )”

slide-50
SLIDE 50

more flexible spec

changer : int * int list -> (int list -> bool)

  • > (int list -> 'a) -> (unit -> 'a) -> 'a

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 ( )

  • therwise

success, call s with a value failure, call k

slide-51
SLIDE 51

changer

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

slide-52
SLIDE 52

changer

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

slide-53
SLIDE 53

examples

  • changer (12, [1,1,1,1,1,1,1,2,4,6])

(fn _ => true) SOME (fn () => NONE); val it = SOME [1,1,1,1,1,1,2,4] : int list option

  • changer (12, [1,1,1,1,1,1,1,2,4,6])

(fn A => length A < 4) SOME (fn () => NONE); val it = SOME [2,4,6] : int list option

slide-54
SLIDE 54

correctness?

  • PROOF Induction on L

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…)

slide-55
SLIDE 55

verbose?

  • We make argument assumptions explicit
  • We indicate flexibility of result type
  • We use accurate math notation
  • The requirements are clear

“For all types t, all total functions p, …yada yada yada”

precise!

slide-56
SLIDE 56

utility

fun change (n, L) p =

fun mkchange (n, L) p =

changer : int * int list -> (int list -> bool)

  • > (int list -> 'a) -> (unit -> 'a) -> 'a

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)

slide-57
SLIDE 57

robust

  • There’s no need to re-do the code if the

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])

  • generous_change (12, [5,5,2,42]) (fn _ => true)

val it = [1,5,5,2] : int list

generous_change : int * int list -> (int list -> bool) -> int list

slide-58
SLIDE 58

lessons

  • May be better to solve a more general problem
  • suitable for recursion, polymorphic
  • Propagate enough information
  • a boolean may not be sufficient
  • Make sure that requirements hold for

recursive calls… so you can use inductive hypothesis that recursive calls work correctly

slide-59
SLIDE 59

reflection

  • We ended up showing how continuations

can be used to solve a problem that we initially solved directly

  • In fact, for every direct-style function

there is a continuation-style function that “does the same job”…

slide-60
SLIDE 60

direct-style

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.

slide-61
SLIDE 61

direct-style

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...”

slide-62
SLIDE 62

continuation-style

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”.

slide-63
SLIDE 63

continuation-style

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”

slide-64
SLIDE 64

continuation?

  • A function that represents

“the rest of a computation” or “what to do next”

  • Expects to be called

with a value (“the result so far”) and computes a final value (an “answer”)

slide-65
SLIDE 65

direct cps

  • Every direct-style function definition

can be turned into a cps function definition

  • a syntax-based transformation
  • The cps version makes control flow and

intermediate values explicit

  • The cps version is also tail recursive

and polymorphic

slide-66
SLIDE 66

factorial

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

slide-67
SLIDE 67

examples

  • fact_cps 3 (fn x => x) = 6
  • fact_cps 3 Int.toString = “6”
  • fact_cps 3 (fn y => fact_cps y Int.toString) = ????
slide-68
SLIDE 68

examples

  • fun prod [] = 1

| prod (x::L) = x * prod L; val prod = fn : int list -> int

  • fun prod_cps [ ] k = k 1

| 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

slide-69
SLIDE 69

examples

  • fun prod [] = 1

| prod (x::L) = x * prod L; val prod = fn : int list -> int

  • fun Prod [ ] k = k 1

| 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)

slide-70
SLIDE 70

examples

  • fun split [ ] = ([ ], [ ])

| 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

  • fun split_cps [ ] k = k ([ ], [ ])

| 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

slide-71
SLIDE 71

connections

  • The cps-version of a direct-style function

“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

slide-72
SLIDE 72

warnings

  • It’s easy to go wrong
  • A cps function NEVER returns…

it calls a continuation instead

  • If ML tells you an unexpected type,

typically less polymorphic, you got it wrong.

The cps version of f is NOT just fun f_cps x k = k(f x)

slide-73
SLIDE 73

next Tuesday

  • Midterm Exam 1
  • Watch for announcements about release

and hand-in instructions

  • Online, at home
  • No cheating or collaborating
  • It’s OK to refer to lecture notes and slides
  • No class on Tuesday

October 13

  • covers material so far, excluding continuation-passing style
  • including map, folds, lists, trees, specs, proofs, work/span,…
slide-74
SLIDE 74

next Thursday

  • Using continuations for backtracking
  • Many search algorithms backtrack

from a dead end, to an earlier choice point, and explore further alternatives

  • Easy to implement… with a failure continuation

call it , maybe!

October 13