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

it passes a value to its continuation,

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

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

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)

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

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

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

!

!

!