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