let rec sum l = match l with | [] -> 0 | h::t -> h + (sum t) let rec sum_tr l a = match l with | [] -> a | h::t -> sum_tr t (a + h) (* continuation: fun x -> add x to running sum *) let rec sum_cps (l: int list) (k: int -> 'k) : 'k = match l with | [] -> k 0 | h::t -> sum_cps t (fun x -> k (x + h)) let _ = sum_cps [1; 2; 3; 4; 5] (fun x -> x) ;; (* CPS as tail-call everything *) (* In CPS, functions *never return*; they always pass their results to another function (the continuation). Since this happens in the tail position, CPS -- if implemented thoroughly, and with TCO -- makes it unnecessary to maintain a function (control) stack. Does this mean CPS converts all O(N)-space programs into O(1)? *) (* Let's rewrite sum_cps from before, with + in CPS style too *) let plus_cps x y k = k (x + y) let sum_cps2 l k = match l with | [] -> k 0 | h::t -> sum_cps t (fun x -> plus_cps x h k) (* Even though the control stack *is* eliminated, the continuation itself grows * (as the "remaining" part of the program increases in size). By the last call * to `sum_cps2`, we have an unevaluated function (a "thunk") of O(N) size! *) (* to fully write a program in CPS, *all* functions must be in CPS ... *) (* Remember that bonus question to write a tail-recursive fold_right? *) let fold_right (f: 'a -> 'b -> 'b) (l: 'a list) (b: 'b) = let rec fr_almost_cps f l (k: 'b -> 'k) : 'k = match l with | [] -> k b | h::t -> fr_almost_cps f t (fun b -> k (f h b)) in fr_almost_cps f l (fun x -> x) ;; (* consider a CPS implementation of / (division) which wishes to explicitly * encode two possible continuations: normal and error (div by 0) *) (* Note now that the two continuations can take different types: * the function can return different types! * The continuations have to return the same type though. *) let div_cps (x: int) (y: int) (k: int -> 'k) (e: string -> 'k) : 'k = if y = 0 then e "Div by zero!" else k (x / y) let _ = div_cps 10 2 (Printf.printf "Result: %d\n") (Printf.printf "Error: %s\n") let _ = div_cps 10 0 (Printf.printf "Result: %d\n") (Printf.printf "Error: %s\n") (* We can use this to short-circuit a function when an exception is detected. *) let rec div_all xs ys k e = match (xs, ys) with | ([], []) -> k [] | (x::xs, y::ys) -> div_cps x y (fun d -> div_all xs ys (fun l -> k (d::l)) e) e | _ -> e "Not the same length!" let _ = div_all [2; 4; 6] [1; 2; 3] (fun x -> x) (fun s -> failwith s) let _ = div_all [2; 4; 6] [0; 1; 2] (fun x -> x) (fun s -> failwith s) let _ = div_all [1; 2; 3] [1; 2] (fun x -> x) (fun s -> failwith s) (* Applications of Continuations *) (* CPS is infrequently used (especially globally) by programmers, but it can be used as an intermediate representation for compilers of functional languages. CPS demonstrates a technique also known as the *callback function*. This is often used in situations where we aren't sure when the result of some function will be available, and so we pass a callback/continuation which will be used to "call back" with the result. E.g., consider the following Node.js (an asynch JavaScript framework) code: function processData (callback) { fetchData(function (err, data) { if (err) { console.log("An error has occurred. Abort everything!"); return callback(err); } data += 1; callback(data); }); } --- Continuations, as a general mechanism for explicitly manipulating the flow of control in a program, have many other applications: - exceptions - coroutines - asynchronous communication *)