CSCI 2041

ADVANCED PROGRAMMING PRINCIPLES

Higher-order Functions: Continuations

Recall that, say, append and sum_tree are not tail-recursive

let rec append l1 l2 = match l1 with
| [] -> l2
| h::t -> h::(append t l2)
let rec sum_tree t = match t with
| Empty -> 0
| Node (v,lt,rt) -> v + (sum_tree lt) + (sum_tree rt)

Continuations as tail recursion

let append l1 l2 =
  let rec app_k l k = match l with
  | [] -> k l2
  | h::t -> app_k t (fun r -> k (h::r))
  in app_k l1 (fun x -> x)

Evaluating append [1;2] [3; 4]:

≡ app_k [1;2] id

≡ app_k [2] k1 (* = fun r1 -> id (1::r1) *)

≡ app_k [] k2 (* = fun r2 -> k1 (2::r2) *)

≡ k2 l2

≡ k2 [3;4]

≡ k1 2::[3,4]

≡ id 1::2::[3,4]

let append l1 l2 =
  let rec app_k l k = match l with
  | [] -> k l2
  | h::t -> app_k t (fun r -> k (h::r))
  in app_k l1 (fun x -> x)

The function k that processes the result is called a continuation.

The practice of passing a function to process the result of a recursive call is called continuation passing style (CPS).

Example: write range in CPS:

let range m n =
  let rec range_k m k =
    if m >= n then k []
    else range_k (m+1) (fun r -> k (m::r))
  in range_k m (fun r -> r)

Another example:

let dfs (t : 'a btree) =
  let rec dfs_k tr k = match tr with
  | Empty -> k []
  | Node (v,lt,rt) ->
     dfs_k lt (fun lr -> dfs_k rt
               (fun rr -> k (lr @ (v::rr)))) in
  dfs_k t (fun x -> x)

Compare to:

let dfs (t: 'a btree) = match t with
| Empty -> []
| Node (v,lt,rt) -> (dfs lt) @ [v] @ (dfs rt)

Exercises: sum_tree_k, preorder_k

Note: what is the type of app_k?

let rec app_k l k = match l with
| [] -> k l2
| h::t -> app_k t (fun rr -> k (h::rr))

'a list -> ('a list -> 'b) -> 'b

The “initial continuation” determines the result type.

Continuations for control flow

Continuations can be used to change control flow and evaluation order:

 let f s =
   let _ = print_string s in s
 let g s =
   let _ = print_endline s in s
 let backwards = (f "hello") ^ (g " world")

Evaluates (g " world") first, then (f "hello")

  let f' s k =
    k (let _ = print_string s in s)
  let forwards =
    f' "hello" (fun s -> s^(g " world"))

Continuations for error (event) handling

Continuations can be useful for dealing with errors:

type result =  Fine of string | StackOverflow | StackUnderflow
type token = C of float | PL (* other ops omitted for space *)
let rpnEval tlist k =
  let rec evl tl stk k = match (tl,stk) with
  | ([],[f]) -> k (Fine (string_of_float f))
  | ([],_) -> k StackOverflow
  | ((C f)::t,_) -> evl t (f::stk) k
  | (PL::t,x::y::stk') -> evl t (x+.y::stk') k
  | (PL::t,_) -> k StackUnderflow
in evl tlist [] k
let result =
  rpnEval tokens (function Fine s -> s
  | StackOverflow -> "Too many constants"
  | StackUnderflow -> "Add without 2 args")

cs2041.org

// reveal.js plugins