tweak advanced
[lambda.git] / miscellaneous_lambda_challenges_and_advanced_topics.mdwn
1 1.      How would you define an operation to reverse a list? (Don't peek at the
2 [[lambda_library]]! Try to figure it out on your own.) Choose whichever
3 implementation of list you like. Even then, there are various strategies you
4 can use.
5
6
7 2.      An advantage of the v3 lists and v3 (aka "Church") numerals is that they
8         have a recursive capacity built into their skeleton. So for many natural
9         operations on them, you won't need to use a fixed point combinator. Why is
10         that an advantage? Well, if you use a fixed point combinator, then the terms
11         you get
12         won't be strongly normalizing: whether their reduction stops at a normal form
13         will depend on what evaluation order you use. Our online [[lambda evaluator]]
14         uses normal-order reduction, so it finds a normal form if there's one to be
15         had. But if you want to build lambda terms in, say, Scheme, and you wanted to
16         roll your own recursion as we've been doing, rather than relying on Scheme's
17         native `let rec` or `define`, then you can't use the fixed-point combinators
18         `Y` or <code>&Theta;</code>. Expressions using them will have non-terminating
19         reductions, with Scheme's eager/call-by-value strategy. There are other
20         fixed-point combinators you can use with Scheme (in the [week 3 notes](/week3/#index7h2) they
21         were <code>Y&prime;</code> and <code>&Theta;&prime;</code>. But even with
22         them, evaluation order still matters: for some (admittedly unusual)
23         evaluation strategies, expressions using them will also be non-terminating.
24
25         The fixed-point combinators may be the conceptual stars. They are cool and
26         mathematically elegant. But for efficiency and implementation elegance, it's
27         best to know how to do as much as you can without them. (Also, that knowledge
28         could carry over to settings where the fixed point combinators are in
29         principle unavailable.)
30
31         This is why the v3 lists and numbers are so lovely. However, one disadvantage
32         to them is that it's relatively inefficient to extract a list's tail, or get a
33         number's predecessor. To get the tail of the list `[a;b;c;d;e]`, one will
34         basically be performing some operation that builds up the tail afresh: at
35         different stages, one will have built up `[e]`, then `[d;e]`, then `[c;d;e]`, and
36         finally `[b;c;d;e]`. With short lists, this is no problem, but with longer lists
37         it takes longer and longer. And it may waste more of your computer's memory
38         than you'd like. Similarly for obtaining a number's predecessor.
39
40         The v1 lists and numbers on the other hand, had the tail and the predecessor
41         right there as an element, easy for the taking. The problem was just that the
42         v1 lists and numbers didn't have recursive capacity built into them, in the
43         way the v3 implementations do.
44
45         A clever approach would marry these two strategies.
46
47         Version 3 makes the list `[a;b;c;d;e]` look like this:
48
49                 \f z. f a (f b (f c (f d (f e z))))
50
51         or in other words:
52
53                 \f z. f a <the result of folding f and z over the tail>
54
55         Instead we could make it look like this:
56
57                 \f z. f a <the tail itself> <the result of folding f and z over the tail>
58
59         That is, now `f` is a function expecting *three* arguments: the head of the
60         current list, the tail of the current list, and the result of continuing to
61         fold `f` over the tail, with a given base value `z`.
62
63         Call this a **version 4** list. The empty list can be the same as in v3:
64
65         <pre><code>empty &equiv; \f z. z</code></pre>
66
67         The list constructor would be:
68
69         <pre><code>make_list &equiv; \h t. \f z. f h t (t f z)</code></pre>
70
71         It differs from the version 3 `make_list` only in adding the extra argument
72         `t` to the new, outer application of `f`.
73
74         Similarly, `five` as a v3 or Church numeral looks like this:
75
76                 \s z. s (s (s (s (s z))))
77
78         or in other words:
79
80                 \s z. s <the result of applying s to z (pred 5)-many times>
81
82         Instead we could make it look like this:
83
84                 \s z. s <pred 5> <the result of applying s to z (pred 5)-many times>
85
86         That is, now `s` is a function expecting *two* arguments: the predecessor of the
87         current number, and the result of continuing to apply `s` to the base value `z`
88         predecessor-many times.
89
90         Jim had the pleasure of "inventing" these implementations himself. However,
91         unsurprisingly, he wasn't the first to do so. See for example [Oleg's report
92         on P-numerals](http://okmij.org/ftp/Computation/lambda-calc.html#p-numerals).
93
94
95
96 3.      **Sets**
97
98         You're now already in a position to implement sets: that is, collections with
99         no intrinsic order where elements can occur at most once. Like lists, we'll
100         understand the basic set structures to be *type-homogenous*. So you might have
101         a set of integers, or you might have a set of pairs of integers, but you
102         wouldn't have a set that mixed both types of elements. Something *like* the
103         last option is also achievable, but it's more difficult, and we won't pursue it
104         now. In fact, we won't talk about sets of pairs, either. We'll just talk about
105         sets of integers. The same techniques we discuss here could also be applied to
106         sets of pairs of integers, or sets of triples of booleans, or sets of pairs
107         whose first elements are booleans, and whose second elements are triples of
108         integers. And so on.
109
110         (You're also now in a position to implement *multi*sets: that is, collections
111         with no intrinsic order where elements can occur multiple times: the multiset
112         {a,a} is distinct from the multiset {a}. But we'll leave these as an exercise.)
113
114         The easiest way to implement sets of integers would just be to use lists. When
115         you "add" a member to a set, you'd get back a list that was either identical to
116         the original list, if the added member already was present in it, or consisted
117         of a new list with the added member prepended to the old list. That is:
118
119                 let empty_set = empty  in
120                 ; see the library for definitions of any and eq
121                 let make_set = \new_member old_set. any (eq new_member) old_set
122                                                         ; if any element in old_set was eq new_member
123                                                         old_set
124                                                         ; else
125                                                         make_list new_member old_set
126
127         Think about how you'd implement operations like `set_union`,
128         `set_intersection`, and `set_difference` with this implementation of sets.
129
130         The implementation just described works, and it's the simplest to code.
131         However, it's pretty inefficient. If you had a 100-member set, and you wanted
132         to create a set which had all those 100-members and some possibly new element
133         `e`, you might need to check all 100 members to see if they're equal to `e`
134         before concluding they're not, and returning the new list. And comparing for
135         numeric equality is a moderately expensive operation, in the first place.
136
137         (You might say, well, what's the harm in just prepending `e` to the list even
138         if it already occurs later in the list. The answer is, if you don't keep track
139         of things like this, it will likely mess up your implementations of
140         `set_difference` and so on. You'll have to do the book-keeping for duplicates
141         at some point in your code. It goes much more smoothly if you plan this from
142         the very beginning.)
143
144         How might we make the implementation more efficient? Well, the *semantics* of
145         sets says that they have no intrinsic order. That means, there's no difference
146         between the set {a,b} and the set {b,a}; whereas there is a difference between
147         the *list* `[a;b]` and the list `[b;a]`. But this semantic point can be respected
148         even if we *implement* sets with something ordered, like list---as we're
149         already doing. And we might *exploit* the intrinsic order of lists to make our
150         implementation of sets more efficient.
151
152         What we could do is arrange it so that a list that implements a set always
153         keeps in elements in some specified order. To do this, there'd have *to be*
154         some way to order its elements. Since we're talking now about sets of numbers,
155         that's easy. (If we were talking about sets of pairs of numbers, we'd use
156         "lexicographic" ordering, where `(a,b) < (c,d)` iff `a < c or (a == c and b <
157         d)`.)
158
159         So, if we were searching the list that implements some set to see if the number
160         `5` belonged to it, once we get to elements in the list that are larger than `5`,
161         we can stop. If we haven't found `5` already, we know it's not in the rest of the
162         list either.
163
164         This is an improvement, but it's still a "linear" search through the list.
165         There are even more efficient methods, which employ "binary" searching. They'd
166         represent the set in such a way that you could quickly determine whether some
167         element fell in one half, call it the left half, of the structure that
168         implements the set, if it belonged to the set at all. Or that it fell in the
169         right half, it it belonged to the set at all. And then the same sort of
170         determination could be made for whichever half you were directed to. And then
171         for whichever quarter you were directed to next. And so on. Until you either
172         found the element or exhausted the structure and could then conclude that the
173         element in question was not part of the set. These sorts of structures are done
174         using **binary trees** (see below).
175
176
177 4.      **Aborting a search through a list**
178
179         We said that the sorted-list implementation of a set was more efficient than
180         the unsorted-list implementation, because as you were searching through the
181         list, you could come to a point where you knew the element wasn't going to be
182         found. So you wouldn't have to continue the search.
183
184         If your implementation of lists was, say v1 lists plus the Y-combinator, then
185         this is exactly right. When you get to a point where you know the answer, you
186         can just deliver that answer, and not branch into any further recursion. If
187         you've got the right evaluation strategy in place, everything will work out
188         fine.
189
190         But what if you're using v3 lists? What options would you have then for
191         aborting a search?
192
193         Well, suppose we're searching through the list `[5;4;3;2;1]` to see if it
194         contains the number `3`. The expression which represents this search would have
195         something like the following form:
196
197                 ..................<eq? 1 3>  ~~>
198                 .................. false     ~~>
199                 .............<eq? 2 3>       ~~>
200                 ............. false          ~~>
201                 .........<eq? 3 3>           ~~>
202                 ......... true               ~~>
203                 ?
204
205         Of course, whether those reductions actually followed in that order would
206         depend on what reduction strategy was in place. But the result of folding the
207         search function over the part of the list whose head is `3` and whose tail is `[2;
208         1]` will *semantically* depend on the result of applying that function to the
209         more rightmost pieces of the list, too, regardless of what order the reduction
210         is computed by. Conceptually, it will be easiest if we think of the reduction
211         happening in the order displayed above.
212
213         Well, once we've found a match between our sought number `3` and some member of
214         the list, we'd like to avoid any further unnecessary computations and just
215         deliver the answer `true` as "quickly" or directly as possible to the larger
216         computation in which the search was embedded.
217
218         With a Y-combinator based search, as we said, we could do this by just not
219         following a recursion branch.
220
221         But with the v3 lists, the fold is "pre-programmed" to continue over the whole
222         list. There is no way for us to bail out of applying the search function to the
223         parts of the list that have head `4` and head `5`, too.
224
225         We *can* avoid *some* unneccessary computation. The search function can detect
226         that the result we've accumulated so far during the fold is now `true`, so we
227         don't need to bother comparing `4` or `5` to `3` for equality. That will simplify the
228         computation to some degree, since as we said, numerical comparison in the
229         system we're working in is moderately expensive.
230
231         However, we're still going to have to traverse the remainder of the list. That
232         `true` result will have to be passed along all the way to the leftmost head of
233         the list. Only then can we deliver it to the larger computation in which the
234         search was embedded.
235
236         It would be better if there were some way to "abort" the list traversal. If,
237         having found the element we're looking for (or having determined that the
238         element isn't going to be found), we could just immediately stop traversing the
239         list with our answer. **Continuations** will turn out to let us do that.
240
241         We won't try yet to fully exploit the terrible power of continuations. But
242         there's a way that we can gain their benefits here locally, without yet having
243         a fully general machinery or understanding of what's going on.
244
245         The key is to recall how our implementations of booleans and pairs worked.
246         Remember that with pairs, we supply the pair "handler" to the pair as *an
247         argument*, rather than the other way around:
248
249                 pair (\x y. add x y)
250
251         or:
252
253                 pair (\x y. x)
254
255         to get the first element of the pair. Of course you can lift that if you want:
256
257         <pre><code>extract_fst &equiv; \pair. pair (\x y. x)</code></pre>
258
259         but at a lower level, the pair is still accepting its handler as an argument,
260         rather than the handler taking the pair as an argument. (The handler gets *the
261         pair's elements*, not the pair itself, as arguments.)
262
263         >       *Terminology*: we'll try to use names of the form `get_foo` for handlers, and
264         names of the form `extract_foo` for lifted versions of them, that accept the
265         lists (or whatever data structure we're working with) as arguments. But we may
266         sometimes forget.
267
268         The v2 implementation of lists followed a similar strategy:
269
270                 v2list (\h t. do_something_with_h_and_t) result_if_empty
271
272         If the `v2list` here is not empty, then this will reduce to the result of
273         supplying the list's head and tail to the handler `(\h t.
274         do_something_with_h_and_t)`.
275
276         Now, what we've been imagining ourselves doing with the search through the v3
277         list is something like this:
278
279
280                 larger_computation (search_through_the_list_for_3) other_arguments
281
282         That is, the result of our search is supplied as an argument (perhaps together
283         with other arguments) to the "larger computation". Without knowing the
284         evaluation order/reduction strategy, we can't say whether the search is
285         evaluated before or after it's substituted into the larger computation. But
286         semantically, the search is the argument and the larger computation is the
287         function to which it's supplied.
288
289         What if, instead, we did the same kind of thing we did with pairs and v2
290         lists? That is, what if we made the larger computation a "handler" that we
291         passed as an argument to the search?
292
293                 the_search (\search_result. larger_computation search_result other_arguments)
294
295         What's the advantage of that, you say. Other than to show off how cleverly
296         you can lift.
297
298         Well, think about it. Think about the difficulty we were having aborting the
299         search. Does this switch-around offer us anything useful?
300
301         It could.
302
303         What if the way we implemented the search procedure looked something like this?
304
305         At a given stage in the search, we wouldn't just apply some function `f` to the
306         head at this stage and the result accumulated so far (from folding the same
307         function, and a base value, to the tail at this stage)...and then pass the result
308         of that application to the embedding, more leftward computation.
309
310         We'd *instead* give `f` a "handler" that expects the result of the current
311         stage *as an argument*, and then evaluates to what you'd get by passing that
312         result leftwards up the list, as before. 
313
314         Why would we do that, you say? Just more flamboyant lifting?
315
316         Well, no, there's a real point here. If we give the function a "handler" that
317         encodes the normal continuation of the fold leftwards through the list, we can
318         also give it other "handlers" too. For example, we can also give it the underlined handler:
319
320
321                 the_search (\search_result. larger_computation search_result other_arguments)
322                                    ------------------------------------------------------------------
323
324         This "handler" encodes the search's having finished, and delivering a final
325         answer to whatever else you wanted your program to do with the result of the
326         search. If you like, at any stage in the search you might just give an argument
327         to *this* handler, instead of giving an argument to the handler that continues
328         the list traversal leftwards. Semantically, this would amount to *aborting* the
329         list traversal! (As we've said before, whether the rest of the list traversal
330         really gets evaluated will depend on what evaluation order is in place. But
331         semantically we'll have avoided it. Our larger computation  won't depend on the
332         rest of the list traversal having been computed.)
333
334         Do you have the basic idea? Think about how you'd implement it. A good
335         understanding of the v2 lists will give you a helpful model.
336
337         In broad outline, a single stage of the search would look like before, except
338         now f would receive two extra, "handler" arguments.
339
340                 f 3 <result of folding f and z over [2; 1]> <handler to continue folding leftwards> <handler to abort the traversal>
341
342         `f`'s job would be to check whether `3` matches the element we're searching for
343         (here also `3`), and if it does, just evaluate to the result of passing `true` to
344         the abort handler. If it doesn't, then evaluate to the result of passing
345         `false` to the continue-leftwards handler.
346
347         In this case, `f` wouldn't need to consult the result of folding `f` and `z` over `[2;
348         1]`, since if we had found the element `3` in more rightward positions of the
349         list, we'd have called the abort handler and this application of `f` to `3` etc
350         would never be needed. However, in other applications the result of folding `f`
351         and `z` over the more rightward parts of the list would be needed. Consider if
352         you were trying to multiply all the elements of the list, and were going to
353         abort (with the result `0`) if you came across any element in the list that was
354         zero. If you didn't abort, you'd need to know what the more rightward elements
355         of the list multiplied to, because that would affect the answer you passed
356         along to the continue-leftwards handler.
357
358         A **version 5** list encodes the kind of fold operation we're envisaging here, in
359         the same way that v3 (and v4) lists encoded the simpler fold operation.
360         Roughly, the list `[5;4;3;2;1]` would look like this:
361
362
363                 \f z continue_leftwards_handler abort_handler.
364                         <fold f and z over [4;3;2;1]>
365                         (\result_of_fold_over_4321. f 5 result_of_fold_over_4321  continue_leftwards_handler abort_handler)
366                         abort_handler
367
368                 ; or, expanding the fold over [4;3;2;1]:
369
370                 \f z continue_leftwards_handler abort_handler.
371                         (\continue_leftwards_handler abort_handler.
372                                 <fold f and z over [3;2;1]>
373                                 (\result_of_fold_over_321. f 4 result_of_fold_over_321 continue_leftwards_handler abort_handler)
374                                 abort_handler
375                         )
376                         (\result_of_fold_over_4321. f 5 result_of_fold_over_4321  continue_leftwards_handler abort_handler)
377                         abort_handler
378
379                 ; and so on             
380                 
381         Remarks: the `larger_computation` handler should be supplied as both the
382         `continue_leftwards_handler` and the `abort_handler` for the leftmost
383         application, where the head `5` is supplied to `f`; because the result of this
384         application should be passed to the larger computation, whether it's a "fall
385         off the left end of the list" result or it's a "I'm finished, possibly early"
386         result. The `larger_computation` handler also then gets passed to the next
387         rightmost stage, where the head `4` is supplied to `f`, as the `abort_handler` to
388         use if that stage decides it has an early answer.
389
390         Finally, notice that we don't have the result of applying `f` to `4` etc given as
391         an argument to the application of `f` to `5` etc. Instead, we pass
392
393                 (\result_of_fold_over_4321. f 5 result_of_fold_over_4321 <one_handler> <another_handler>)
394
395         *to* the application of `f` to `4` as its "continue" handler. The application of `f`
396         to `4` can decide whether this handler, or the other, "abort" handler, should be
397         given an argument and constitute its result.
398
399
400         I'll say once again: we're using temporally-loaded vocabulary throughout this,
401         but really all we're in a position to mean by that are claims about the result
402         of the complex expression semantically depending only on this, not on that. A
403         demon evaluator who custom-picked the evaluation order to make things maximally
404         bad for you could ensure that all the semantically unnecessary computations got
405         evaluated anyway. We don't have any way to prevent that. Later,
406         we'll see ways to *semantically guarantee* one evaluation order rather than
407         another. Though even then the demonic evaluation-order-chooser could make it
408         take unnecessarily long to compute the semantically guaranteed result. Of
409         course, in any real computing environment you'll know you're dealing with a
410         fixed evaluation order and you'll be able to program efficiently around that.
411
412         In detail, then, here's what our v5 lists will look like:
413
414                 let empty = \f z continue_handler abort_handler. continue_handler z  in
415                 let make_list = \h t. \f z continue_handler abort_handler.
416                         t f z (\sofar. f h sofar continue_handler abort_handler) abort_handler  in
417                 let isempty = \lst larger_computation. lst
418                                 ; here's our f
419                                 (\hd sofar continue_handler abort_handler. abort_handler false)
420                                 ; here's our z
421                                 true
422                                 ; here's the continue_handler for the leftmost application of f
423                                 larger_computation
424                                 ; here's the abort_handler
425                                 larger_computation  in
426                 let extract_head = \lst larger_computation. lst
427                                 ; here's our f
428                                 (\hd sofar continue_handler abort_handler. continue_handler hd)
429                                 ; here's our z
430                                 junk
431                                 ; here's the continue_handler for the leftmost application of f
432                                 larger_computation
433                                 ; here's the abort_handler
434                                 larger_computation  in
435                 let extract_tail = ; left as exercise
436
437         These functions are used like this:
438
439                 let my_list = make_list a (make_list b (make_list c empty) in
440                 extract_head my_list larger_computation
441
442         If you just want to see `my_list`'s head, the use `I` as the
443         `larger_computation`.
444
445         What we've done here does take some work to follow. But it should be within
446         your reach. And once you have followed it, you'll be well on your way to
447         appreciating the full terrible power of continuations.
448
449         <!-- (Silly [cultural reference](http://www.newgrounds.com/portal/view/33440).) -->
450
451         Of course, like everything elegant and exciting in this seminar, [Oleg
452         discusses it in much more
453         detail](http://okmij.org/ftp/Streams.html#enumerator-stream).
454
455         *Comments*:
456
457         1.      The technique deployed here, and in the v2 lists, and in our implementations
458                 of pairs and booleans, is known as **continuation-passing style** programming.
459
460         2.      We're still building the list as a right fold, so in a sense the
461                 application of `f` to the leftmost element `5` is "outermost". However,
462                 this "outermost" application is getting lifted, and passed as a *handler*
463                 to the next right application. Which is in turn getting lifted, and
464                 passed to its next right application, and so on. So if you
465                 trace the evaluation of the `extract_head` function to the list `[5;4;3;2;1]`,
466                 you'll see `1` gets passed as a "this is the head sofar" answer to its
467                 `continue_handler`; then that answer is discarded and `2` is
468                 passed as a "this is the head sofar" answer to *its* `continue_handler`,
469                 and so on. All those steps have to be evaluated to finally get the result
470                 that `5` is the outer/leftmost head of the list. That's not an efficient way
471                 to get the leftmost head.
472
473                 We could improve this by building lists as left folds when implementing them
474                 as continuation-passing style folds. We'd just replace above:
475         
476                         let make_list = \h t. \f z continue_handler abort_handler.
477                                 f h z (\z. t f z continue_handler abort_handler) abort_handler
478
479                 now `extract_head` should return the leftmost head directly, using its `abort_handler`:
480
481                         let extract_head = \lst larger_computation. lst
482                                         (\hd sofar continue_handler abort_handler. abort_handler hd)
483                                         junk
484                                         larger_computation
485                                         larger_computation
486
487         3.      To extract tails efficiently, too, it'd be nice to fuse the apparatus developed
488                 in these v5 lists with the ideas from the v4 lists, above.
489                 But that also is left as an exercise.
490
491
492 5.      **Implementing (self-balancing) trees**
493
494         In [[Assignment3]] we proposed a very ad-hoc-ish implementation of trees.
495
496         Think about how you'd implement them in a more principled way. You could
497         use any of the version 1 -- version 5 implementation of lists as a model.
498
499         To keep things simple, we'll stick to binary trees. A node will either be a
500         *leaf* of the tree, or it will have exactly two children.
501
502         There are two kinds of trees to think about. In one sort of tree, it's only
503         the tree's *leaves* that are labeled:
504
505                     +
506                    / \ 
507           +   3
508                  / \
509                 1   2 
510
511         Linguists often use trees of this sort. The inner, non-leaf nodes of the
512 tree have associated values. But what values they are can be determined from
513 the structure of the tree and the values of the node's left and right children.
514 So the inner node doesn't need its own independent label.
515
516         In another sort of tree, the tree's inner nodes are also labeled:
517
518                     4
519                    / \ 
520           2   5
521                  / \
522                 1   3 
523
524         When you want to efficiently arrange an ordered collection, so that it's
525         easy to do a binary search through it, this is the way you usually structure
526         your data.
527
528         These latter sorts of trees can helpfully be thought of as ones where
529         *only* the inner nodes are labeled. Leaves can be thought of as special,
530         dead-end branches with no label:
531         
532                            .4.
533                           /   \ 
534                          2     5
535                         / \   / \
536                    1   3  x x
537                   / \ / \
538                  x  x x  x
539
540         In our earlier discussion of lists, we said they could be thought of as
541         data structures of the form:
542
543                 Empty_list | Non_empty_list (its_head, its_tail)
544
545         And that could in turn be implemented in v2 form as:
546
547                 the_list (\head tail. non_empty_handler) empty_handler
548
549         Similarly, the leaf-labeled tree:
550
551                     +
552                    / \ 
553           +   3
554                  / \
555                 1   2 
556
557         can be thought of as a data structure of the form:
558
559                 Node (its_left_subtree, its_right_subtree) | Leaf (its_label)
560
561         and that could be implemented in v2 form as:
562
563                 the_tree (\left right. node_handler) (\label. lead_handler)
564
565         And the node-labeled tree:
566
567                            .4.
568                           /   \ 
569                          2     5
570                         / \   / \
571                    1   3  x x
572                   / \ / \
573                  x  x x  x
574
575         can be thought of as a data structure of the form:
576
577                 Node (its_left_subtree, its_label, its_right_subtree) | Leaf
578
579         and that could be implemented in v2 form as:
580
581                 the_tree (\left label right. node_handler) leaf_result
582
583
584         What would correspond to "folding" a function `f` and base value `z` over a
585         tree? Well, if it's an empty tree:
586
587                 x
588
589         we should presumably get back `z`. And if it's a simple, non-empty tree:
590
591                   1
592                  / \
593                 x   x
594
595         we should expect something like `f z 1 z`, or `f <result of folding f and z
596         over left subtree> label_of_this_node <result of folding f and z over right
597         subtree>`. (It's not important what order we say `f` has to take its arguments
598         in.)
599
600         A v3-style implementation of node-labeled trees, then, might be:
601
602                 let empty_tree = \f z. z  in
603                 let make_tree = \left label right. \f z. f (left f z) label (right f z)  in
604                 ...
605
606         Think about how you might implement other tree operations, such as getting
607 the label of the root (topmost node) of a tree; extracting the left subtree of
608 a node; and so on.
609
610         Think about different ways you might implement v3-style trees.
611
612         If you had one tree and wanted to make a larger tree out of it, adding in a
613 new element, how would you do that?
614
615         When using trees to represent linguistic structures, one doesn't have
616 latitude about *how* to build a larger tree. The linguistic structure you're
617 trying to represent will determine where the new element should be placed, and
618 where the previous tree should be placed.
619
620         However, when using trees as a computational tool, one usually does have
621 latitude about how to structure a larger tree---in the same way that we had the
622 freedom to implement our sets with lists whose members were just appended in
623 the order we built the set up, or instead with lists whose members were ordered
624 numerically.
625
626         When building a new tree, one strategy for where to put the new element and
627 where to put the existing tree would be to always lean towards a certain side.
628 For instance, to add the element `2` to the tree:
629
630                   1
631                  / \
632                 x   x
633
634         we might construct the following tree:
635
636                   1
637                  / \
638                 x   2
639                    / \
640                   x   x
641
642         or perhaps we'd do it like this instead:
643
644                   2
645                  / \
646                 x   1
647                    / \
648                   x   x
649
650         However, if we always leaned to the right side in this way, then the tree
651 would get deeper and deeper on that side, but never on the left:
652
653                   1
654                  / \
655                 x   2
656                    / \
657                   x   3
658                      / \
659                         x   4
660                        / \
661                       x   5
662                          / \
663                         x   x
664
665         and that wouldn't be so useful if you were using the tree as an arrangement
666 to enable *binary searches* over the elements it holds. For that, you'd prefer
667 the tree to be relatively "balanced", like this:
668
669                            .4.
670                           /   \ 
671                          2     5
672                         / \   / \
673                    1   3  x x
674                   / \ / \
675                  x  x x  x
676
677         Do you have any ideas about how you might efficiently keep the new trees
678 you're building pretty "balanced" in this way?
679
680         This is a large topic in computer science. There's no need for you to learn
681 any of the various strategies that they've developed for doing this. But
682 thinking in broad brush-strokes about what strategies might be promising will
683 help strengthen your understanding of trees, and useful ways to implement them
684 in a purely functional setting like the lambda calculus.
685
686
687
688