Recall that, say, append
and sum_tree
are not tail-recursive
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:
Another example:
Compare to:
Exercises: sum_tree_k
, preorder_k
Note: what is the type of app_k
?
'a list ->
('a list -> 'b) ->
'b
The “initial continuation” determines the result type.
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")
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 *)
cs2041.org