70949bf7d69b4ae1e7ba564b83e246183b52df7d
[lambda.git] / topics / week12_abortable_traversals.mdwn
1 ## Aborting a search through a list ##
2
3 We've talked about different implementations of lists in the Lambda Calculus; and we've also talked about using lists to implement other data structures, like sets. One thing we observed is that if you're going to implement a set using a list (this isn't an especially efficient implementation of a set, but let's do the best we can with it), it's helpful to make sure that the list is always sorted. That way, as you're searching through the list, you might come to a point before the end where you knew the element you're looking for wasn't going to be found anymore. So you wouldn't have to continue the search.
4
5 If you were implementing lists with `letrec` or a fixed point combinator, then at that point you could just have your traversal function return some appropriate result, instead of requesting that the recursion continue.
6
7 But what if you were using our right-fold or left-fold implementation of lists, rather than using `letrec` or a fixed point combinator? It's true that `letrec` and fixed point combinators are cool. But if you use them, the terms you construct won't be strongly normalizing. Whether their reduction stops at a normal form will depend on what evaluation order is operative. For efficiency, it can also be helpful to know how to get by without those powerful devices when they're not strictly necessary. Plus that knowledge could carry over to settings where `letrec` is no longer available, even in principle.
8
9 When dealing with a right-fold or left-fold implementation of lists, if your traversal function returns a result, that result automatically gets passed to the next stage of the traversal, as the updated seed value from the previous steps. If we make the seed value type complex, we could signal to the rest of the traversal that the job is done, they don't need to do any more work. For example, the seed value might be a pair of `false` and some default value until we reach a certain stage, and all the traversal steps until that stage have to do their normal work, but once we've gotten to the stage where we're ready to return a result, we make the seed value be a pair of `true` and the result we've computed. Then all the later traversal steps see that `true` and just pass the existing seed pair on to the rest of the traversal. At the end, we throw away the `true` and take the second member of the final seed value as our desired result.
10
11 That will work OK, but we still have to go through every step of the traversal. Let's think about whether we can modify the right-fold and/or left-fold implementation of lists to allow for a genuine early abort. We'd like to avoid any unnecessary steps of the traversal. If we've worked out our result mid-way through the traversal, we want to be able to deliver it _immediately_ to the larger computation in which our traversal was embedded. The problem with the fold-based implementations of lists we've got so far is that they are pre-programmed to traverse the whole list. We'd have to implement the folds differently to be able to achieve what we're now envisaging.
12
13 We worked out such an implementation in the homework session on Wed April 22. The scheme we used was that, whereas before our traversal functions would have an interface like this:
14
15     \current_list_element seed_value_so_far. do_something
16
17 Our traversal functions will instead now have an interface like this:
18
19     \current_list_element seed_value_so_far done_handler keep_going_handler.
20       if ... then done_handler (some_result) else keep_going_handler (another_result)
21
22 and we worked out that a left-fold implementation of the list `[10,20,30,40]` could look like this:
23
24     \f z done_handler. f 10 z done_handler (\z. [20,30,40] f z done_handler)
25
26 This is all reminiscent of the way in which our encodings of triples and pairs and such too their handler functions as arguments. Remember with triples, we say:
27
28     triple (\x y z. add x y)
29
30 or:
31
32     triple (\x y z. z)
33
34 to get the last element of the triple. Of course you can wrap this up in _another_ function, that takes the triple as _its_ argument, but at bottom, you are going to be supplying the handler to the triple. And then the handler gets the triple's _elements_, not the triple itself, as arguments. (Another of our implementations of lists followed a similar strategy.)
35
36 Something similar is happening here, where both the traversal function `f` and the fold function that implements the list both take such handler arguments. The only not-completely-straightforward thing going on in the fold function is that the `keep_going_handler` we pass to the traversal function `f` *encodes* how the fold should continue, using the updated seed value `z` from the current step, *without actually computing* the rest of the traversal. It's up to the body of `f` to decide whether to invoke the rest of the traversal, by supplying a value to that `keep_going_handler`, or to finish the traversal right now by supply a value to the `done_handler` instead.
37
38 A question came up in the session of why we need the `done_handler` in this scheme. We could just eliminate it and have the traversal function `f` choose between simply returning a value --- that'd abort the traversal, the way we did above by passing the value to `done_handler` --- or instead supplying a value to the `keep_going_handler`. And the answer is that yes, in this particular scheme, that is correct. (In a few weeks when we look at delimited continuations in terms of `reset`/`shift`, you'll see that what we just described is how the `abort` operation gets implemented in terms of `shift`.) However, sometimes it helps to express a basic case a bit more verbosely than seems immediately necessary, because then later generalizations will look more natural. That's true here. So let's just use the implementation as we've written it. If you prefer, you can just make the `done_handler` be the identity function.
39
40 With that general scheme, here is the empty list:
41
42     \f z done_handler. done_handler z
43
44 and here is the `cons` operation:
45
46     \x xs. \f z done_handler. f x z done_handler (\z. xs f z done_handler)
47
48 (The latter can just be read off of our construction of `[10,20,30,40]`; I just substituted `x` for `10` and `xs` for `[20, 30, 40]`.)
49
50 Here's an example of how to get the head of such a list:
51
52     xs (\x z done keep_going. done x) err done_handler
53
54 `err` is what's returned if you ask for the `head` of the empty list.
55
56 Here's how to get the length of such a list:
57
58     xs (\x z done keep_going. keep_going (succ z)) 0 done_handler
59
60 Here there is no opportunity to abort early with a correct value, so our traversal function always delivers its output to the `keep_going` handler.
61
62 Here's how to get the last element of such a list:
63
64     xs (\x z done keep_going. keep_going x) err done_handler
65
66 This is similar to getting the first element, except that each step delivers its output to the `keep_going` handler rather than to the `done` handler. That ensures that we will only get the output of the last step, when the traversal function is applied to the last member of the list. If the list is empty, then we'll get the `err` value, just as with the function that tries to extract the list's head.
67
68 One thing to note is that there are limits to how much you can immunize yourself against doing unnecessary work. A demon evaluator who got to custom-pick the evaluation order (including doing reductions underneath lambdas when he wanted to) could ensure that lots of unnecessary computations got performed, despite your best efforts. We don't yet have any way to prevent that. (Later we will see some ways to *computationally force* the evaluation order we prefer. Of course, in any real computing environment you'll mostly know what evaluation order you're dealing with, and you can mostly program efficiently around that.) The current scheme at least makes our result not *computationally depend on* what happens further on in the traversal, once we've passed a result to the `done_handler`. We don't even depend on the later steps in the traversal cooperating to pass our result through.
69
70 All of that gave us a left-fold implementation of lists. (Perhaps if you were _aiming_ for a left-fold implementation of lists, you would make the traversal function `f` take its `current_list_element` and `seed_value` arguments in the flipped order, but let's not worry about that.)
71
72 Now, let's think about how to get a right-fold implementation. It's not profoundly different, but it does require us to change our interface a little. Our left-fold implementation of `[10,20,30,40]`, above, looked like this (now we abbreviate some of the variables):
73
74     \f z d. f 10 z d (\z. [20,30,40] f z d)
75
76 Expanding the definition of `[20,30,40]`, and all the successive tails, this comes to:
77
78     \f z d. f 10 z d (\z. f 20 z d (\z. f 30 z d (\z. f 40 z d d)))
79
80 For a right-fold implementation, that should instead look like roughly like this:
81
82     \f z d. f 40 z d (\z. f 30 z d (\z. f 20 z d (\z. f 10 z d d)))
83
84 Now suppose we had just the implementation of the tail of the list, `[20,30,40]`, that is:
85
86     \f z d. f 40 z d (\z. f 30 z d (\z. f 20 z d d))
87
88 How should we take that value and transform it into the preceding value, which represents `10` consed onto that tail? I can't see how to do it in a general way, and I expect it's just not possible. Essentially what we want is to take that second `d` in the innermost function `\z. f 20 z d d`, we want to replace that second `d` with something like `(\z. f 10 z d d)`. But how can we replace just the second `d` without also replacing the first `d`, and indeed all the other bound occurrences of `d` in the expansion of `[20,30,40]`.
89
90 The difficulty here is that our traversal function `f` expects two handlers, but we are only giving the fold function we implement the list as a single handler. That single handler gets fed twice to the traversal function. One time it may be transformed, but at the end of the traversal, as with `\z. f 20 z d d`, there's nothing left to do to "keep going", so here it's just the single handler `d` fed to `f` twice. But we can see that in order to implement `cons` for a right-folding traversal, we don't want it to be the single handler `d` fed to `f` twice. It'd work better if we implemented `[20,30,40]` like this:
91
92     \f z d g. f 40 z d (\z. f 30 z d (\z. f 20 z d g))
93
94 Notice that now the fold function we implement the list as takes *two* handlers, `d` (for "done") and `g` (for "keep going"). Generally we'll *invoke* the fold function *by supplying the same handler function to both of these*. However, it's still useful to have the list be defined so that they're separate arguments. For now we can `cons` `10` onto the list by just substituting `(\z. f 10 z d g)` in for the bound `g`. That is:
95
96     [10,20,30,40] ≡ \f z d g. [20,30,40] f z d (\z. f 10 z d g)
97
98 Spelling this out, here are the implementations of the functions we defined before, only now for the right-fold lists:
99
100     null = \f z d g. g z
101     cons x xs = \f z d g. xs f z d (\z. f x z d g)
102     head xs = xs (\x z d g. g x) err done_handler done_handler
103     length xs = xs (\x z d g. g (succ z)) 0 done_handler done_handler
104     last xs = xs (\x z d g. d x) err done_handler done_handler
105
106 *Exercise*: when considering just the implementation of `null`, both `\f z d g. g z` and `\f z d g. d z` may seem like reasonable candidates. What would go wrong with the rest of our scheme is we had instead used the latter?
107
108 To extract tails efficiently, too, it'd be nice to merge the apparatus developed above with the ideas from [[another of our implementations of lists|week3_lists#v3-lists]], where we passed the traversal function `f` not merely the current element and *result of traversing the list's tail* (or in the present case, a modified `keep_going` handler which encodes how to do that), but also *the list's tail itself*. That would make some computations more efficient. But we leave this as an exercise.
109
110 Of course, like everything elegant and exciting in this seminar, [Oleg
111 discusses it in much more
112 detail](http://okmij.org/ftp/Streams.html#enumerator-stream).
113
114
115
116
117
118 <!--
119
120 We said that the sorted-list implementation of a set was more efficient than
121 the unsorted-list implementation, because as you were searching through the
122 list, you could come to a point where you knew the element wasn't going to be
123 found. So you wouldn't have to continue the search.
124
125 If your implementation of lists was, say v1 lists plus the Y-combinator, then
126 this is exactly right. When you get to a point where you know the answer, you
127 can just deliver that answer, and not branch into any further recursion. If
128 you've got the right evaluation strategy in place, everything will work out
129 fine.
130
131 But what if we wanted to use v3 lists instead?
132
133 >       Why would we want to do that? The advantage of the v3 lists and v3 (aka
134 "Church") numerals is that they have their recursive capacity built into their
135 very bones. So for many natural operations on them, you won't need to use a fixed
136 point combinator.
137
138 >       Why is that an advantage? Well, if you use a fixed point combinator, then
139 the terms you get won't be strongly normalizing: whether their reduction stops
140 at a normal form will depend on what evaluation order you use. Our online
141 [[lambda evaluator]] uses normal-order reduction, so it finds a normal form if
142 there's one to be had. But if you want to build lambda terms in, say, Scheme,
143 and you wanted to roll your own recursion as we've been doing, rather than
144 relying on Scheme's native `let rec` or `define`, then you can't use the
145 fixed-point combinators `Y` or <code>&Theta;</code>. Expressions using them
146 will have non-terminating reductions, with Scheme's eager/call-by-value
147 strategy. There are other fixed-point combinators you can use with Scheme (in
148 the [week 3 notes](/week3/#index7h2) they were <code>Y&prime;</code> and
149 <code>&Theta;&prime;</code>. But even with them, evaluation order still
150 matters: for some (admittedly unusual) evaluation strategies, expressions using
151 them will also be non-terminating.
152
153 >       The fixed-point combinators may be the conceptual stars. They are cool and
154 mathematically elegant. But for efficiency and implementation elegance, it's
155 best to know how to do as much as you can without them. (Also, that knowledge
156 could carry over to settings where the fixed point combinators are in principle
157 unavailable.)
158
159
160 So again, what if we're using v3 lists? What options would we have then for
161 aborting a search or list traversal before it runs to completion?
162
163 Suppose we're searching through the list `[5;4;3;2;1]` to see if it
164 contains the number `3`. The expression which represents this search would have
165 something like the following form:
166
167         ..................<eq? 1 3>  ~~>
168         .................. false     ~~>
169         .............<eq? 2 3>       ~~>
170         ............. false          ~~>
171         .........<eq? 3 3>           ~~>
172         ......... true               ~~>
173         ?
174
175 Of course, whether those reductions actually followed in that order would
176 depend on what reduction strategy was in place. But the result of folding the
177 search function over the part of the list whose head is `3` and whose tail is `[2;
178 1]` will *semantically* depend on the result of applying that function to the
179 more rightmost pieces of the list, too, regardless of what order the reduction
180 is computed by. Conceptually, it will be easiest if we think of the reduction
181 happening in the order displayed above.
182
183 Once we've found a match between our sought number `3` and some member of
184 the list, we'd like to avoid any further unnecessary computations and just
185 deliver the answer `true` as "quickly" or directly as possible to the larger
186 computation in which the search was embedded.
187
188 With a Y-combinator based search, as we said, we could do this by just not
189 following a recursion branch.
190
191 But with the v3 lists, the fold is "pre-programmed" to continue over the whole
192 list. There is no way for us to bail out of applying the search function to the
193 parts of the list that have head `4` and head `5`, too.
194
195 We *can* avoid *some* unneccessary computation. The search function can detect
196 that the result we've accumulated so far during the fold is now `true`, so we
197 don't need to bother comparing `4` or `5` to `3` for equality. That will simplify the
198 computation to some degree, since as we said, numerical comparison in the
199 system we're working in is moderately expensive.
200
201 However, we're still going to have to traverse the remainder of the list. That
202 `true` result will have to be passed along all the way to the leftmost head of
203 the list. Only then can we deliver it to the larger computation in which the
204 search was embedded.
205
206 It would be better if there were some way to "abort" the list traversal. If,
207 having found the element we're looking for (or having determined that the
208 element isn't going to be found), we could just immediately stop traversing the
209 list with our answer. **Continuations** will turn out to let us do that.
210
211 We won't try yet to fully exploit the terrible power of continuations. But
212 there's a way that we can gain their benefits here locally, without yet having
213 a fully general machinery or understanding of what's going on.
214
215 The key is to recall how our implementations of booleans and pairs worked.
216 Remember that with pairs, we supply the pair "handler" to the pair as *an
217 argument*, rather than the other way around:
218
219         pair (\x y. add x y)
220
221 or:
222
223         pair (\x y. x)
224
225 to get the first element of the pair. Of course you can lift that if you want:
226
227 <pre><code>extract_fst &equiv; \pair. pair (\x y. x)</code></pre>
228
229 but at a lower level, the pair is still accepting its handler as an argument,
230 rather than the handler taking the pair as an argument. (The handler gets *the
231 pair's elements*, not the pair itself, as arguments.)
232
233 >       *Terminology*: we'll try to use names of the form `get_foo` for handlers, and
234 names of the form `extract_foo` for lifted versions of them, that accept the
235 lists (or whatever data structure we're working with) as arguments. But we may
236 sometimes forget.
237
238 The v2 implementation of lists followed a similar strategy:
239
240         v2list (\h t. do_something_with_h_and_t) result_if_empty
241
242 If the `v2list` here is not empty, then this will reduce to the result of
243 supplying the list's head and tail to the handler `(\h t.
244 do_something_with_h_and_t)`.
245
246 Now, what we've been imagining ourselves doing with the search through the v3
247 list is something like this:
248
249
250         larger_computation (search_through_the_list_for_3) other_arguments
251
252 That is, the result of our search is supplied as an argument (perhaps together
253 with other arguments) to the "larger computation". Without knowing the
254 evaluation order/reduction strategy, we can't say whether the search is
255 evaluated before or after it's substituted into the larger computation. But
256 semantically, the search is the argument and the larger computation is the
257 function to which it's supplied.
258
259 What if, instead, we did the same kind of thing we did with pairs and v2
260 lists? That is, what if we made the larger computation a "handler" that we
261 passed as an argument to the search?
262
263         the_search (\search_result. larger_computation search_result other_arguments)
264
265 What's the advantage of that, you say. Other than to show off how cleverly
266 you can lift.
267
268 Well, think about it. Think about the difficulty we were having aborting the
269 search. Does this switch-around offer us anything useful?
270
271 It could.
272
273 What if the way we implemented the search procedure looked something like this?
274
275 At a given stage in the search, we wouldn't just apply some function `f` to the
276 head at this stage and the result accumulated so far (from folding the same
277 function, and a base value, to the tail at this stage)...and then pass the result
278 of that application to the embedding, more leftward computation.
279
280 We'd *instead* give `f` a "handler" that expects the result of the current
281 stage *as an argument*, and then evaluates to what you'd get by passing that
282 result leftwards up the list, as before. 
283
284 Why would we do that, you say? Just more flamboyant lifting?
285
286 Well, no, there's a real point here. If we give the function a "handler" that
287 encodes the normal continuation of the fold leftwards through the list, we can
288 also give it other "handlers" too. For example, we can also give it the underlined handler:
289
290
291         the_search (\search_result. larger_computation search_result other_arguments)
292                            ------------------------------------------------------------------
293
294 This "handler" encodes the search's having finished, and delivering a final
295 answer to whatever else you wanted your program to do with the result of the
296 search. If you like, at any stage in the search you might just give an argument
297 to *this* handler, instead of giving an argument to the handler that continues
298 the list traversal leftwards. Semantically, this would amount to *aborting* the
299 list traversal! (As we've said before, whether the rest of the list traversal
300 really gets evaluated will depend on what evaluation order is in place. But
301 semantically we'll have avoided it. Our larger computation  won't depend on the
302 rest of the list traversal having been computed.)
303
304 Do you have the basic idea? Think about how you'd implement it. A good
305 understanding of the v2 lists will give you a helpful model.
306
307 In broad outline, a single stage of the search would look like before, except
308 now `f` would receive two extra, "handler" arguments. We'll reserve the name `f` for the original fold function, and use `f2` for the function that accepts two additional handler arguments. To get the general idea, you can regard these as interchangeable. If the extra precision might help, then you can pay attention to when we're talking about the handler-taking `f2` or the original `f`. You'll only be *supplying* the `f2` function; the idea will be that the behavior of the original `f` will be implicitly encoded in `f2`'s behavior.
309
310         f2 3 <sofar value that would have resulted from folding f and z over [2; 1]> <handler to continue folding leftwards> <handler to abort the traversal>
311
312 `f2`'s job would be to check whether `3` matches the element we're searching for
313 (here also `3`), and if it does, just evaluate to the result of passing `true` to
314 the abort handler. If it doesn't, then evaluate to the result of passing
315 `false` to the continue-leftwards handler.
316
317 In this case, `f2` wouldn't need to consult the result of folding `f` and `z`
318 over `[2; 1]`, since if we had found the element `3` in more rightward
319 positions of the list, we'd have called the abort handler and this application
320 of `f2` to `3` etc would never be needed. However, in other applications the
321 result of folding `f` and `z` over the more rightward parts of the list would
322 be needed. Consider if you were trying to multiply all the elements of the
323 list, and were going to abort (with the result `0`) if you came across any
324 element in the list that was zero. If you didn't abort, you'd need to know what
325 the more rightward elements of the list multiplied to, because that would
326 affect the answer you passed along to the continue-leftwards handler.
327
328 A **version 5** list encodes the kind of fold operation we're envisaging here,
329 in the same way that v3 (and [v4](/advanced_lambda/#index1h1)) lists encoded
330 the simpler fold operation. Roughly, the list `[5;4;3;2;1]` would look like
331 this:
332
333
334         \f2 z continue_leftwards_handler abort_handler.
335                 <fold f2 and z over [4;3;2;1]>
336                 (\result_of_folding_over_4321. f2 5 result_of_folding_over_4321  continue_leftwards_handler abort_handler)
337                 abort_handler
338
339         ; or, expanding the fold over [4;3;2;1]:
340
341         \f2 z continue_leftwards_handler abort_handler.
342                 (\continue_leftwards_handler abort_handler.
343                         <fold f2 and z over [3;2;1]>
344                         (\result_of_folding_over_321. f2 4 result_of_folding_over_321 continue_leftwards_handler abort_handler)
345                         abort_handler
346                 )
347                 (\result_of_folding_over_4321. f2 5 result_of_folding_over_4321  continue_leftwards_handler abort_handler)
348                 abort_handler
349
350         ; and so on
351         
352 Remarks: the `larger_computation` handler should be supplied as both the
353 `continue_leftwards_handler` and the `abort_handler` for the leftmost
354 application, where the head `5` is supplied to `f2`; because the result of this
355 application should be passed to the larger computation, whether it's a "fall
356 off the left end of the list" result or it's a "I'm finished, possibly early"
357 result. The `larger_computation` handler also then gets passed to the next
358 rightmost stage, where the head `4` is supplied to `f2`, as the `abort_handler` to
359 use if that stage decides it has an early answer.
360
361 Finally, notice that we're not supplying the application of `f2` to `4` etc as an argument to the application of `f2` to `5` etc---at least, not directly. Instead, we pass
362
363         (\result_of_folding_over_4321. f2 5 result_of_folding_over_4321 <one_handler> <another_handler>)
364
365 *to* the application of `f2` to `4` as its "continue" handler. The application of `f2`
366 to `4` can decide whether this handler, or the other, "abort" handler, should be
367 given an argument and constitute its result.
368
369
370 I'll say once again: we're using temporally-loaded vocabulary throughout this,
371 but really all we're in a position to mean by that are claims about the result
372 of the complex expression semantically depending only on this, not on that. A
373 demon evaluator who custom-picked the evaluation order to make things maximally
374 bad for you could ensure that all the semantically unnecessary computations got
375 evaluated anyway. We don't yet know any way to prevent that. Later, we'll see
376 ways to *guarantee* one evaluation order rather than another. Of
377 course, in any real computing environment you'll know in advance that you're
378 dealing with a fixed evaluation order and you'll be able to program efficiently
379 around that.
380
381 In detail, then, here's what our v5 lists will look like:
382
383         let empty = \f2 z continue_handler abort_handler. continue_handler z  in
384         let make_list = \h t. \f2 z continue_handler abort_handler.
385                 t f2 z (\sofar. f2 h sofar continue_handler abort_handler) abort_handler  in
386         let isempty = \lst larger_computation. lst
387                         ; here's our f2
388                         (\hd sofar continue_handler abort_handler. abort_handler false)
389                         ; here's our z
390                         true
391                         ; here's the continue_handler for the leftmost application of f2
392                         larger_computation
393                         ; here's the abort_handler
394                         larger_computation  in
395         let extract_head = \lst larger_computation. lst
396                         ; here's our f2
397                         (\hd sofar continue_handler abort_handler. continue_handler hd)
398                         ; here's our z
399                         junk
400                         ; here's the continue_handler for the leftmost application of f2
401                         larger_computation
402                         ; here's the abort_handler
403                         larger_computation  in
404         let extract_tail = ; left as exercise
405
406 These functions are used like this:
407
408         let my_list = make_list a (make_list b (make_list c empty) in
409         extract_head my_list larger_computation
410
411 If you just want to see `my_list`'s head, the use `I` as the
412 `larger_computation`.
413
414 What we've done here does take some work to follow. But it should be within
415 your reach. And once you have followed it, you'll be well on your way to
416 appreciating the full terrible power of continuations.
417
418 (Silly [cultural reference](http://www.newgrounds.com/portal/view/33440).)
419
420
421 >       *Comments*:
422
423 >       1.      The technique deployed here, and in the v2 lists, and in our
424 >       implementations of pairs and booleans, is known as 
425 >       **continuation-passing style** programming.
426
427 >       2.      We're still building the list as a right fold, so in a sense the
428 >       application of `f2` to the leftmost element `5` is "outermost". However,
429 >       this "outermost" application is getting lifted, and passed as a *handler*
430 >       to the next right application. Which is in turn getting lifted, and
431 >       passed to its next right application, and so on. So if you
432 >       trace the evaluation of the `extract_head` function to the list `[5;4;3;2;1]`,
433 >       you'll see `1` gets passed as a "this is the head sofar" answer to its
434 >       `continue_handler`; then that answer is discarded and `2` is
435 >       passed as a "this is the head sofar" answer to *its* `continue_handler`,
436 >       and so on. All those steps have to be evaluated to finally get the result
437 >       that `5` is the outer/leftmost head of the list. That's not an efficient way
438 >       to get the leftmost head.
439 >       
440 >       We could improve this by building lists as **left folds**. What's that?
441 >       
442 >       Well, the right fold of `f` over a list `[a;b;c;d;e]`, using starting value z, is:
443 >       
444 >                       f a (f b (f c (f d (f e z))))
445 >       
446 >       The left fold on the other hand starts combining `z` with elements from the left. `f z a` is then combined with `b`, and so on:
447 >       
448 >                       f (f (f (f (f z a) b) c) d) e
449 >       
450 >       or, if we preferred the arguments to each `f` flipped:
451 >       
452 >                       f e (f d (f c (f b (f a z))))
453 >       
454 >       Recall we implemented v3 lists as their own right-fold functions. We could
455 >       instead implement lists as their own left-fold functions. To do that with our
456 >       v5 lists, we'd replace above:
457 >       
458 >                       let make_list = \h t. \f2 z continue_handler abort_handler.
459 >                               f2 h z (\z. t f2 z continue_handler abort_handler) abort_handler
460 >       
461 >       Having done that, now `extract_head` can return the leftmost head
462 >       directly, using its `abort_handler`:
463 >       
464 >                       let extract_head = \lst larger_computation. lst
465 >                                       (\hd sofar continue_handler abort_handler. abort_handler hd)
466 >                                       junk
467 >                                       larger_computation
468 >                                       larger_computation
469 >       
470 >       3.      To extract tails efficiently, too, it'd be nice to fuse the apparatus
471 >       developed in these v5 lists with the ideas from 
472 >       [v4](/advanced_lambda/#index1h1) lists. But that is left as an exercise.
473
474 -->