X-Git-Url: http://lambda.jimpryor.net/git/gitweb.cgi?p=lambda.git;a=blobdiff_plain;f=week4.mdwn;h=fae6c4623ad496f3c20ee9aeeb9166fbc53ff88c;hp=bc154aea1195830c5858a716055ec250c4229a3e;hb=6ff495fd9985a3db4df959b5deea75ea525cfef0;hpb=5fa25870987fce498870f06907422cee6a0bb8b1
diff --git a/week4.mdwn b/week4.mdwn
index bc154aea..fae6c462 100644
--- a/week4.mdwn
+++ b/week4.mdwn
@@ -250,7 +250,7 @@ So, if we were searching the list that implements some set to see if the number
we can stop. If we haven't found `5` already, we know it's not in the rest of the
list either.
-*Comment*: This is an improvement, but it's still a "linear" search through the list.
+> *Comment*: This is an improvement, but it's still a "linear" search through the list.
There are even more efficient methods, which employ "binary" searching. They'd
represent the set in such a way that you could quickly determine whether some
element fell in one half, call it the left half, of the structure that
@@ -276,10 +276,39 @@ can just deliver that answer, and not branch into any further recursion. If
you've got the right evaluation strategy in place, everything will work out
fine.
-But what if you're using v3 lists? What options would you have then for
-aborting a search?
-
-Well, suppose we're searching through the list `[5;4;3;2;1]` to see if it
+But what if we wanted to use v3 lists instead?
+
+> Why would we want to do that? The advantage of the v3 lists and v3 (aka
+"Church") numerals is that they have their recursive capacity built into their
+very bones. So for many natural operations on them, you won't need to use a fixed
+point combinator.
+
+> Why is that an advantage? Well, if you use a fixed point combinator, then
+the terms you get won't be strongly normalizing: whether their reduction stops
+at a normal form will depend on what evaluation order you use. Our online
+[[lambda evaluator]] uses normal-order reduction, so it finds a normal form if
+there's one to be had. But if you want to build lambda terms in, say, Scheme,
+and you wanted to roll your own recursion as we've been doing, rather than
+relying on Scheme's native `let rec` or `define`, then you can't use the
+fixed-point combinators `Y` or Θ
. Expressions using them
+will have non-terminating reductions, with Scheme's eager/call-by-value
+strategy. There are other fixed-point combinators you can use with Scheme (in
+the [week 3 notes](/week3/#index7h2) they were Y′
and
+Θ′
. But even with them, evaluation order still
+matters: for some (admittedly unusual) evaluation strategies, expressions using
+them will also be non-terminating.
+
+> The fixed-point combinators may be the conceptual stars. They are cool and
+mathematically elegant. But for efficiency and implementation elegance, it's
+best to know how to do as much as you can without them. (Also, that knowledge
+could carry over to settings where the fixed point combinators are in principle
+unavailable.)
+
+
+So again, what if we're using v3 lists? What options would we have then for
+aborting a search or list traversal before it runs to completion?
+
+Suppose we're searching through the list `[5;4;3;2;1]` to see if it
contains the number `3`. The expression which represents this search would have
something like the following form:
@@ -541,38 +570,41 @@ Of course, like everything elegant and exciting in this seminar, [Oleg
discusses it in much more
detail](http://okmij.org/ftp/Streams.html#enumerator-stream).
-*Comments*:
-
-1. The technique deployed here, and in the v2 lists, and in our implementations
- of pairs and booleans, is known as **continuation-passing style** programming.
-
-2. We're still building the list as a right fold, so in a sense the
- application of `f` to the leftmost element `5` is "outermost". However,
- this "outermost" application is getting lifted, and passed as a *handler*
- to the next right application. Which is in turn getting lifted, and
- passed to its next right application, and so on. So if you
- trace the evaluation of the `extract_head` function to the list `[5;4;3;2;1]`,
- you'll see `1` gets passed as a "this is the head sofar" answer to its
- `continue_handler`; then that answer is discarded and `2` is
- passed as a "this is the head sofar" answer to *its* `continue_handler`,
- and so on. All those steps have to be evaluated to finally get the result
- that `5` is the outer/leftmost head of the list. That's not an efficient way
- to get the leftmost head.
-
- We could improve this by building lists as left folds when implementing them
- as continuation-passing style folds. We'd just replace above:
-
- let make_list = \h t. \f z continue_handler abort_handler.
- f h z (\z. t f z continue_handler abort_handler) abort_handler
-
- now `extract_head` should return the leftmost head directly, using its `abort_handler`:
-
- let extract_head = \lst larger_computation. lst
- (\hd sofar continue_handler abort_handler. abort_handler hd)
- junk
- larger_computation
- larger_computation
-
-3. To extract tails efficiently, too, it'd be nice to fuse the apparatus developed
- in these v5 lists with the ideas from [v4](/advanced/#index1h1) lists.
- But that also is left as an exercise.
+> *Comments*:
+
+> 1. The technique deployed here, and in the v2 lists, and in our
+> implementations of pairs and booleans, is known as
+> **continuation-passing style** programming.
+
+> 2. We're still building the list as a right fold, so in a sense the
+> application of `f` to the leftmost element `5` is "outermost". However,
+> this "outermost" application is getting lifted, and passed as a *handler*
+> to the next right application. Which is in turn getting lifted, and
+> passed to its next right application, and so on. So if you
+> trace the evaluation of the `extract_head` function to the list `[5;4;3;2;1]`,
+> you'll see `1` gets passed as a "this is the head sofar" answer to its
+> `continue_handler`; then that answer is discarded and `2` is
+> passed as a "this is the head sofar" answer to *its* `continue_handler`,
+> and so on. All those steps have to be evaluated to finally get the result
+> that `5` is the outer/leftmost head of the list. That's not an efficient way
+> to get the leftmost head.
+>
+> We could improve this by building lists as left folds when implementing them
+> as continuation-passing style folds. We'd just replace above:
+>
+> let make_list = \h t. \f z continue_handler abort_handler.
+> f h z (\z. t f z continue_handler abort_handler) abort_handler
+>
+> now `extract_head` should return the leftmost head directly, using its
+> `abort_handler`:
+>
+> let extract_head = \lst larger_computation. lst
+> (\hd sofar continue_handler abort_handler. abort_handler hd)
+> junk
+> larger_computation
+> larger_computation
+>
+> 3. To extract tails efficiently, too, it'd be nice to fuse the apparatus
+> developed in these v5 lists with the ideas from [v4](/advanced/#index1h1)
+> lists. But that is left as an exercise.
+