index b991c52..3ec13ae 100644 (file)
@@ -4,7 +4,6 @@ Using continuations to solve the same-fringe problem
The problem
-----------

The problem
-----------

-We've seen two solutions to the same fringe problem so far.
The problem, recall, is to take two trees and decide whether they have
the same leaves in the same order.

The problem, recall, is to take two trees and decide whether they have
the same leaves in the same order.

@@ -26,47 +25,48 @@ let tc = Node (Leaf 1, Node (Leaf 3, Leaf 2));;
So `ta` and `tb` are different trees that have the same fringe, but
`ta` and `tc` are not.

So `ta` and `tb` are different trees that have the same fringe, but
`ta` and `tc` are not.

+We've seen two solutions to the same fringe problem so far.
The simplest solution is to map each tree to a list of its leaves,
then compare the lists.  But because we will have computed the entire
fringe before starting the comparison, if the fringes differ in an
early position, we've wasted our time examining the rest of the trees.

The second solution was to use tree zippers and mutable state to
The simplest solution is to map each tree to a list of its leaves,
then compare the lists.  But because we will have computed the entire
fringe before starting the comparison, if the fringes differ in an
early position, we've wasted our time examining the rest of the trees.

The second solution was to use tree zippers and mutable state to
-simulate coroutines (see [[coroutines and aborts]]).  In that
-solution, we pulled the zipper on the first tree until we found the
-next leaf, then stored the zipper structure in the mutable variable
-while we turned our attention to the other tree.  This solution is
-efficient: the zipper doesn't visit any leaves beyond the first
-mismatch.
+simulate coroutines (see [[coroutines and aborts]], and
+[[assignment8]]).  In that solution, we pulled the zipper on the first
+tree until we found the next leaf, then stored the zipper structure in
+a mutable variable while we turned our attention to the other tree.
+This solution is efficient: the zipper doesn't visit any leaves beyond
+the first mismatch.

Since zippers are just continuations reified, we expect that the
solution in terms of zippers can be reworked using continuations, and
this is indeed the case.  Your assignment is to show how.

Since zippers are just continuations reified, we expect that the
solution in terms of zippers can be reworked using continuations, and
this is indeed the case.  Your assignment is to show how.

-TO-DO LIST for solving the problem
-----------------------------------
+The first step is to review your answer to [[assignment8]], and make
+sure you understand what is going on.

-1.  Review the simple but inefficient solution (easy).
-2.  Understand the zipper/mutable state solution in [[coroutines and aborts]] (harder).

-3.  Two obvious approaches:
+Two strategies for solving the problem
+--------------------------------------

-a.  Review the list-zipper/list-continuation example given in
+
+1.  Review the list-zipper/list-continuation example given in
class in [[from list zippers to continuations]]; then
figure out how to re-functionalize the zippers used in the zipper
solution.

class in [[from list zippers to continuations]]; then
figure out how to re-functionalize the zippers used in the zipper
solution.

-b.  Review how the continuation-flavored tree\_monadizer managed to
-map a tree to a list of its leaves, in [[manipulating trees with
-monads]].  Spend some time trying to understand exactly what it does:
-compute the tree-to-list transformation for a tree with two leaves,
-performing all beta reduction by hand using the definitions for
-bind\_continuation, unit\_continuation and so on.  If you take this
-route, study the description of streams (a particular kind of data
-structure) below.  The goal will be to arrange for the
-continuation-flavored tree_monadizer to transform a tree into a stream
-instead of into a list.  Once you've done that, completing the
-same-fringe problem will be easy.
+2.  Review how the continuation-flavored `tree_monadizer` managed to
+    map a tree to a list of its leaves, in [[manipulating trees with monads]].
+    Spend some time trying to understand exactly what it
+    does: compute the tree-to-list transformation for a tree with two
+    leaves, performing all beta reduction by hand using the
+    definitions for `continuation_bind`, `continuation_unit` and so on.
+    If you take this route, study the description of **streams** (a
+    particular kind of data structure) below.  The goal will be to
+    arrange for the continuation-flavored `tree_monadizer` to transform
+    a tree into a stream instead of into a list.  Once you've done
+    that, completing the same-fringe problem will be easy.

-------------------------------------

-------------------------------------

@@ -83,9 +83,7 @@ requirement of the problem; in particular, that when the trees differ
in an early position, your code does not waste time visiting the rest
of the tree.  One way to do this is to add print statements to your
functions so that every time you visit a leaf (say), a message is
in an early position, your code does not waste time visiting the rest
of the tree.  One way to do this is to add print statements to your
functions so that every time you visit a leaf (say), a message is
-printed on the output.  If two trees differ in the middle of their
-fringe, you should show that your solution prints debugging
-information for the first half of the fringe, but then stops.
+printed on the output. (In OCaml: `print_int 1` prints an `int`, `print_string "foo"` prints a `string`, `print_newline ()` prints a line break, and `print_endline "foo"` prints a string followed by a line break.) If two trees differ in the middle of their fringe, you should show that your solution prints debugging information for the first half of the fringe, but then stops.

4.  What if you had some reason to believe that the trees you were
going to compare were more likely to differ in the rightmost region?

4.  What if you had some reason to believe that the trees you were
going to compare were more likely to differ in the rightmost region?
@@ -95,8 +93,8 @@ right to left?
Streams
-------

Streams
-------

-A stream is like a list in that it contains a series of objects.  It
-differs from a list in that the tail of the list is left uncomputed
+A stream is like a list in that it wraps a series of elements of a single type.
+It differs from a list in that the tail of the series is left uncomputed
until needed.  We will turn the stream on and off by thunking it (see
class notes for [[week6]] on thunks, as well as [[assignment5]]).

until needed.  We will turn the stream on and off by thunking it (see
class notes for [[week6]] on thunks, as well as [[assignment5]]).

@@ -104,9 +102,9 @@ class notes for [[week6]] on thunks, as well as [[assignment5]]).

There is a special stream called `End` that represents a stream that
contains no (more) elements, analogous to the empty list `[]`.

There is a special stream called `End` that represents a stream that
contains no (more) elements, analogous to the empty list `[]`.
-Streams that are not empty contain a first object paired with a
+Streams that are not empty contain a first object, paired with a
thunked stream representing the rest of the series.  In order to get
thunked stream representing the rest of the series.  In order to get
-access to the next element in the stream, we must forced the thunk by
+access to the next element in the stream, we must *force* the thunk by
applying it to the unit.  Watch the behavior of this stream in detail.
This stream delivers the natural numbers, in order: 1, 2, 3, ...

applying it to the unit.  Watch the behavior of this stream in detail.
This stream delivers the natural numbers, in order: 1, 2, 3, ...

@@ -118,17 +116,66 @@ val make_int_stream : int -> int stream = [fun]
val int_stream : int stream = Next (1, [fun])         (* First element: 1 *)

# let tail = match int_stream with Next (i, rest) -> rest;;
val int_stream : int stream = Next (1, [fun])         (* First element: 1 *)

# let tail = match int_stream with Next (i, rest) -> rest;;
-val tail : unit -> int stream = <fun>                 (* Tail: a thunk *)
+val tail : unit -> int stream = [fun]                 (* Tail: a thunk *)

(* Force the thunk to compute the second element *)
# tail ();;

(* Force the thunk to compute the second element *)
# tail ();;
-- : int stream = Next (2, [fun])
+- : int stream = Next (2, [fun])                      (* Second element: 2 *)

# match tail () with Next (_, rest) -> rest ();;

# match tail () with Next (_, rest) -> rest ();;
-- : int stream = Next (3, <fun>)
+- : int stream = Next (3, [fun])                      (* Third element: 3 *)
</pre>

You can think of `int_stream` as a functional object that provides
access to an infinite sequence of integers, one at a time.  It's as if
we had written `[1;2;...]` where `...` meant "continue for as long as
some other process needs new integers".
</pre>

You can think of `int_stream` as a functional object that provides
access to an infinite sequence of integers, one at a time.  It's as if
we had written `[1;2;...]` where `...` meant "continue for as long as
some other process needs new integers".
+
+
+<!--
+With streams in hand, we need only rewrite our continuation tree
+monadizer so that instead of mapping trees to lists, it maps them to
+
+       # tree_monadize (fun a k -> a :: k a) t1 (fun t -> []);;
+       - : int list = [2; 3; 5; 7; 11]
+
+as above, we have
+
+        # tree_monadize (fun i k -> Next (i, fun () -> k ())) t1 (fun _ -> End);;
+        - : int stream = Next (2, <fun>)
+
+We can see the first element in the stream, the first leaf (namely,
+2), but in order to see the next, we'll have to force a thunk.
+
+Then to complete the same-fringe function, we simply convert both
+trees into leaf-streams, then compare the streams element by element.
+The code is entirely routine, but for the sake of completeness, here it is:
+
+       let rec compare_streams stream1 stream2 =
+               match stream1, stream2 with
+               | End, End -> true (* Done!  Fringes match. *)
+               | Next (next1, rest1), Next (next2, rest2) when next1 = next2 -> compare_streams (rest1 ()) (rest2 ())
+               | _ -> false;;
+
+       let same_fringe t1 t2 =
+         let stream1 = tree_monadize (fun i k -> Next (i, fun () -> k ())) t1 (fun _ -> End) in
+         let stream2 = tree_monadize (fun i k -> Next (i, fun () -> k ())) t2 (fun _ -> End) in
+         compare_streams stream1 stream2;;
+
+Notice the forcing of the thunks in the recursive call to
+`compare_streams`.  So indeed:
+
+       # same_fringe ta tb;;
+       - : bool = true
+       # same_fringe ta tc;;
+       - : bool = false
+
+Now, you might think that this implementation is a bit silly, since in
+order to convert the trees to leaf streams, our `tree_monadizer`
+function has to visit every node in the tree, so we'd have to traverse
+the entire tree at some point.  But you'd be wrong: part of what gets
+suspended in the thunking of the stream is the computation of the rest