let rec map f xs = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl) map_cps f l k = match l with | [] -> k [] | hd::tl -> map_cps f tl (fun r -> k((f hd)::r)) Theorem: map_cps f xs id == map f xs (In English: map_cps does the same thing as map) *********************************************************************** Attempt a straightforward proof Theorem: map_cps f xs id == map f xs Proof by induction over the structure of xs, in 2 cases: xs=[] and xs=hd::tl Case xs = []: IMS map_cps f [] id == map f [] (1) map_cps f [] id LHS (2) == id [] eval map_cps, branch 1 (3) == [] eval id (4) == map f [] inv eval map, branch 1 Case complete. Case xs = hd::tl IMS map_cps f hd::tl id == map f hd::tl IH: map_cps f tl id == map f tl (1) map_cps f hd::tl id LHS (2) == map_cps f tl (fun r -> id((f hd)::r)) eval map_cps, branch 2 ... (fun r -> id ((f hd)::r)) != id, so we can't apply the IH here. We're stuck. *********************************************************************** *********************************************************************** When this happens, we've learned to attempt to prove a more general lemma that we can then use as a justification in the proof we really want to write. To do so, we should figure out a relationship between the two sides of our theorem beyond just (hopefully) equality. What is the invariant from step to step to step? In lecture, we saw: eval1 e == eval2 e 0 become: (eval1 e) + n == eval2 e n What can we do here? We're pushing all the pieces that we should have done so far into k. And eventually we're going to have to call k to actually get those pieces done. But for this to equal the non-CPS version at this step, we'd have to do that same deferred work on the result from the non-CPS version too, or else they wouldn't get the same answer. For example: map_cps (+ 1) [2;3;4] (fun r -> ((+ 1) 1)::r) != map (+ 1) [2;3;4] (Because [2;3;4;5] != [3;4;5]) But: map_cps (+ 1) [2;3;4] (fun r -> ((+ 1) 1)::r) == (fun r -> ((+1) 1)::r) (map (+ 1) [2;3;4]) (Because [2;3;4;5] == [2;3;4;5]) So this seems to be a workable invariant, and we can write our Lemma and try to prove it: For all lists xs, functions f, and continuations k, prove that: map_cps f xs k == k (map f xs) ( In English: at any step of the computation, the cps-converted map does the same thing as calling that step's continuation on the result of the original map on the remaining subproblem. Aside: this works not just for the continuations we'll actually get, but for any continuation! But that's a leap many aren't ready to take, so they may be more comfortable with a more concrete example: In a specific case that we'll want later, which might prove elucidating for those struggling to understnand, consider: map_cps f l id does the same thing as id (map f l) ) Proof by induction over the structure of xs, in 2 cases: xs=[] and xs=xhd::xtl Case xs = [] Pick an arbitrary continuation k. IMS: map_cps f [] k == k(map f []) (1) map_cps f [] k LHS (2) == k [] eval map_cps, branch 1 (3) == k (map f [] ) inv. eval map, branch 1 Case Complete. Case xs = xhd::xtl Pick an arbitrary continuation k. IMS: map_cps f xhd::xtl k == k(map f xhd::xtl) IH : map_cps f xtl k' == k'(map f xtl) (1) map_cps f xhd::xtl k LHS (2) == match xhd::xtl with ... Eval map_cps (3) == map_cps f tl (fun r -> k((f xhd)::r)) Eval match (4) == (fun r -> k((f xhd)::r)) (map f xtl) IH ((fun r->...) as k') (5) == k((f xhd)::(map f xtl)) eval anon fun ((map f xtl) as r) (6) == k(map f xhd::xtl) inv. eval map, branch 2 Case complete. QED for the lemma. *********************************************************************** *********************************************************************** Apply lemma to the instance of it that is the theorem we wanted Let's come back to our actual theorem: map_cps f xs id == map f xs Proof by simple equational reasoning: (1) map_cps f xs id LHS (2) id ( map f xs) Lemma (3) map f xs eval id QED ***********************************************************************