Anda di halaman 1dari 34

15-150 Fall 2014

Lecture 12
Stephen Brookes

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.

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, !
it passes a value to its continuation,
to get an answer.

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)

direct

cps

Every direct-style function definition

can be turned into a cps function definition


a purely syntactic transformation


The cps version makes control flow and


intermediate values explicit

The cps version is also tail recursive

factorial
(* fact : int -> int *)
fun fact n = !
if n=0 then 1 else n * fact(n-1)
(* fact : int -> (int -> a) -> a *)

direct
style
not tail recursive

continuation
style

fun fact n k = !
if n=0 then k 1 else fact(n-1)(fn x => k(n * x))
tail recursive

examples
fact 3 (fn x => x) = 6

fact 3 Int.toString = 6

fact 3 (fn y -> fact y Int.toString) = 720

specs
(* fact : int -> int *)

(* REQUIRES n 0 *)

(* ENSURES fact(n) = n! *)

there is an integer v!
such that fact(n) = v,!
and v is equal to n!

(* fact : int -> (int -> a) -> a *)

(* REQUIRES n 0 *)

(* ENSURES fact n k = k(n!) *)
there is an integer v!
such that fact n k = k v,!
and v is equal to n!

proof
Let ans be a type, and let P(n) be:

For all k : int -> ans,
fact n k = k(fact n)

Prove n0. P(n)


Use induction on n
fact 3 (fn m => m+1) = 7

polymorphic
The cps factorial has a polymorphic type

int -> (int -> a) -> a

Why? Because it does the same thing


no matter what the continuation is

(passes a value to the continuation)
(and that value doesnt dependent on the continuation)
(and the cps factorial doesnt care what the continuation does)

cps
When applied, a cps function calls its
continuation at most once

Passes the same value, regardless of the


continuation

Has the same control flow as direct-style


fun fact n = if n=0 then 1 else n * fact(n-1)
fun fact n k = !
if n=0 then k 1 else fact(n-1)(fn x => k(n*x))

not cps
fun fact n k =

if n=0 then k 1 else n * fact(n-1) k
fact : int -> (int -> int) -> int
fact 3 (fn m => m+1) = 12

not cps
fun fact n k =

if n=0 then 1 else fact(n-1) (fn x => k(x*n))
fact : int -> (int -> int) -> int
fact 3 (fn m => m+1) = 1

counting a tree
count : int tree -> int
count : int tree -> (int -> a) -> a
fun count Empty = 0

| count (Node(l,x,r)) = count l + x + count r
fun count Empty k = k 0

| count (Node(l, x, r)) k =

count l (fn m => count r (fn n => k(m+x+n))

not cps
fun count Empty k = k 0

| count (Node(left, x, right)) k =

count left k + x + count right k
Why not?
Whats its type?
What goes wrong?

tail recursion
A recursive function definition is tail recursive
if each recursive call is in tail position,
the last thing the function does before returning.
fun fact n =!
if n=0 then 1 else n * fact(n-1)
fun factacc(n, a) =!
if n=0 then a else factacc(n-1, n*a)
fun fact n k = !
if n=0 then k 1 else fact(n-1) (fn x => k(n * x))

tail call elimination


Tail calls can be implemented efficiently in a space-efficient manner. !
!

When a function is called, the argument values get pushed onto a stack, !
with a return address for the value returned by the call. !
!

For tail calls, we can re-use the same stack frame for arguments
and leave the return address unchanged: the result of the tail call !
needs to get returned to the original call site.
Execution of factacc(3, 1)

With tail call elimination

call factacc (3, 1)!


call factacc (2, 3)!
call factacc (1, 6)!
call factacc (0, 6)!
return 6!
return 6!
return 6!
return 6

call factacc (3, 1)



replace args with (2, 3)

replace args with (1, 6)

replace args with (0, 6)

return 6

backtracking
Many search algorithms backtrack from a
dead end to an earlier choice point to
explore further alternatives

Its easy to implement backtracking


with a failure continuation

n queens
Queens attack on row, column, or diagonal

Task: put n queens safely on an n-by-n board

before we start
Number of ways to put 8 queens on board

64 * 63 * 62 * 61 * 60 * 59 * 58 *57

8*7*6*5*4*3*2*1

> 4,000,000,000

Number of ways using different columns


8 * 7 * 6 * 5 * 4 * 3 * 2 * 1 = 40,320

Number of safe ways

92

Fast algorithm for this problem?


PRICELESS

board
type row = int!
type col = int!
type pos = row * col!
type sol = pos list

threat
threat : pos * pos -> bool

fun threat((x,y), (i,j)) =!


(x=i) orelse !
(y=j) orelse!
(x+y = i+j) orelse !
(x-y = i-j)
threat(p, q) = true

iff p attacks q

conflict
(* conflict : pos * pos list -> bool *)
fun conflict (p, nil) = false!
| conflict (p, q::qs) = threat(p, q) orelse conflict(p, qs)
1

conflict (p, [q1,...,qk]) = false


if threat(p, qi) = false
for each i

q2
1

q5

q4

conflict (p, [q1,...,qk]) = true


if threat(p, qi) = true
for some i

q3
q1

safe
safe : pos list -> bool
fun safe [ ] = true

| safe (q::qs) = if conflict(q, qs)
then false
else safe(qs)
safe [q1,...,qk] = true
iff for all ij,

qi and qj are on different

rows, columns and diagonals

solutions
qs is a

partial solution

for rows 1 through i

if
safe qs = true &
map (fn (i, j) => i) qs = [i, ..., 1]
A full solution to the n-queens problem

is a partial solution qs for rows 1 through n
such that every (i,j) in qs has 1 j n

failure continuations
Many search algorithms can be solved elegantly
using failure continuations

Use a failure continuation to say what to do


if the search reaches a dead end

theres no intermediate result (partial solution)

to carry on with... so typically a failure continuation


is a function with a dummy argument ( ) : unit

strategy
A recursive function that maintains

a partial solution and tries to grow it by


adding a queen in the next row

Its arguments should indicate


the board size
a partial solution
the next row
the columns still to be tried, in the next row
what to do if we reach a dead end

try
try : int * row * col list * sol -> (unit -> sol option) -> sol option

try(n, i, A, qs) fc
REQUIRES: 1 i n and A is a sublist of [1,. . .,n] !
& qs is a partial solution for rows 1 through i-1 !
!

ENSURES:!
try (n, i, A, qs) fc = SOME(qs)
!
where qs is a full solution extending qs!
with a queen in row i at a column in A, if there is one!
try (n, i, A, qs) fc = fc( )
!
otherwise

try(n, i, A, qs) fc
partial solution

on rows 1...i-1

columns to be

tried for row i

If A = [ ], there is no solution extending qs, so fail

fc( )

conflict((i,j), qs)=true

If A = j::B,!
try(n, i, B, qs) fc
either (i,j) is attacked by qs,
!
so try columns from B for row i!
or its safe to extend qs with (i,j); !
if i=n, (i,j)::qs is a full solution;!
otherwise, look for a solution extending (i,j)::qs in row i+1, !
and if this fails, backtrack to try columns from B for row i.
conflict((i,j), qs)=false

try(n, i+1, [1,...,n], (i,j)::qs)


(fn ( ) => try(n, i, B, qs) fc)

try
fun try (n, i, A, qs) fc =!
case A of!
[ ] => fc( )!
| j::B => if conflict((i, j), qs)!
then try (n, i, B, qs) fc!
else if i = n!
then SOME( (i, j)::qs )!
else!
try(n, i+1, upto 1 n, (i, j)::qs)!
(fn ( ) => try (n, i, B, qs) fc)

queens
queens : int -> sol option
REQUIRES: n>0 !
!

ENSURES: !
queens n = SOME qs!
where qs is an n-queens solution,!
if there is one; !
!
queens n = NONE!
otherwise

queens
Look for solution extending [ ]

with a queen in row 1

fun queens n =!
try (n, 1, upto 1 n, nil) !
(fn ( ) => NONE)!
Pessimistic

failure continuation

results
queens 3 = NONE !
!

queens 4 = SOME [(4,3),(3,1),(2,4),(1,2)]!


!

queens 5 = SOME [(5,4),(4,2),(3,5),(2,3),(1,1)] !


!

queens 8 = SOME [(8,4),(7,2),(6,7),(5,3),(4,6),(3,8),(2,5),(1,1)]!


!

queens 20 = SOME [(20,11),(19,6),(18,14),(17,7),(16,10),...]