Merge branch 'master' of ssh://server.philosophy.fas.nyu.edu/Users/lambda/lambda
authorChris Barker <barker@kappa.(none)>
Sun, 3 Oct 2010 18:33:30 +0000 (14:33 -0400)
committerChris Barker <barker@kappa.(none)>
Sun, 3 Oct 2010 18:33:30 +0000 (14:33 -0400)
12 files changed:
advanced_lambda.mdwn [new file with mode: 0644]
assignment2.mdwn
assignment3.mdwn
assignment4.mdwn [new file with mode: 0644]
code/tokens.js
hints/assignment_2_hint.mdwn [moved from assignment_2_hint_1.mdwn with 100% similarity]
implementing_trees.mdwn [new file with mode: 0644]
index.mdwn
lambda_library.mdwn [moved from arithmetic.mdwn with 83% similarity]
new_stuff.mdwn [new file with mode: 0644]
week4.mdwn
week5.mdwn [new file with mode: 0644]

diff --git a/advanced_lambda.mdwn b/advanced_lambda.mdwn
new file mode 100644 (file)
index 0000000..6760ae7
--- /dev/null
@@ -0,0 +1,93 @@
+[[!toc]]
+
+
+#Version 4 lists: Efficiently extracting tails#
+
+An advantage of the v3 lists and v3 (aka "Church") numerals is that they
+have a recursive capacity built into their skeleton. 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 <code>&Theta;</code>. 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 <code>Y&prime;</code> and <code>&Theta;&prime;</code>. 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.)
+
+This is why the v3 lists and numbers are so lovely. However, one disadvantage
+to them is that it's relatively inefficient to extract a list's tail, or get a
+number's predecessor. To get the tail of the list `[a;b;c;d;e]`, one will
+basically be performing some operation that builds up the tail afresh: at
+different stages, one will have built up `[e]`, then `[d;e]`, then `[c;d;e]`, and
+finally `[b;c;d;e]`. With short lists, this is no problem, but with longer lists
+it takes longer and longer. And it may waste more of your computer's memory
+than you'd like. Similarly for obtaining a number's predecessor.
+
+The v1 lists and numbers on the other hand, had the tail and the predecessor
+right there as an element, easy for the taking. The problem was just that the
+v1 lists and numbers didn't have recursive capacity built into them, in the
+way the v3 implementations do.
+
+A clever approach would marry these two strategies.
+
+Version 3 makes the list `[a;b;c;d;e]` look like this:
+
+       \f z. f a (f b (f c (f d (f e z))))
+
+or in other words:
+
+       \f z. f a <the result of folding f and z over the tail>
+
+Instead we could make it look like this:
+
+       \f z. f a <the tail itself> <the result of folding f and z over the tail>
+
+That is, now `f` is a function expecting *three* arguments: the head of the
+current list, the tail of the current list, and the result of continuing to
+fold `f` over the tail, with a given base value `z`.
+
+Call this a **version 4** list. The empty list can be the same as in v3:
+
+<pre><code>empty &equiv; \f z. z</code></pre>
+
+The list constructor would be:
+
+<pre><code>make_list &equiv; \h t. \f z. f h t (t f z)</code></pre>
+
+It differs from the version 3 `make_list` only in adding the extra argument
+`t` to the new, outer application of `f`.
+
+Similarly, `five` as a v3 or Church numeral looks like this:
+
+       \s z. s (s (s (s (s z))))
+
+or in other words:
+
+       \s z. s <the result of applying s to z (pred 5)-many times>
+
+Instead we could make it look like this:
+
+       \s z. s <pred 5> <the result of applying s to z (pred 5)-many times>
+
+That is, now `s` is a function expecting *two* arguments: the predecessor of the
+current number, and the result of continuing to apply `s` to the base value `z`
+predecessor-many times.
+
+Jim had the pleasure of "inventing" these implementations himself. However,
+unsurprisingly, he wasn't the first to do so. See for example [Oleg's report
+on P-numerals](http://okmij.org/ftp/Computation/lambda-calc.html#p-numerals).
+
+
index 3e93256..5d75a85 100644 (file)
@@ -107,7 +107,7 @@ For these exercises, assume that `LIST` is the result of evaluating:
 
 
 <OL start=16>
-<LI>What would be the result of evaluating (see [[Assignment 2 hint 1]] for a hint):
+<LI>What would be the result of evaluating (see [[hints/Assignment 2 hint]] for a hint):
 
        LIST make-list empty
 
index 6f4f3f6..8c22dfa 100644 (file)
@@ -9,40 +9,38 @@ assignment much faster and more secure.
 Recall that version 1 style lists are constructed like this (see
 [[lists and numbers]]):
 
-<pre>
-; booleans
-let true = \x y. x in
-let false = \x y. y in
-let and = \l r. l (r true false) false in
-
-; version 1 lists
-let makePair = \f s g. g f s in
-let fst = true in
-let snd = false in
-let nil = makePair true meh in
-let isNil = \x. x fst in
-let makeList = \h t. makePair false (makePair h t) in
-let head = \l. isNil l err (l snd fst) in
-let tail = \l. isNil l err (l snd snd) in
-
-; a list of numbers to experiment on
-let mylist = makeList 1 (makeList 2 (makeList 3 nil)) in
-
-; a fixed-point combinator for defining recursive functions 
-let Y = \f. (\h. f (h h)) (\h. f (h h)) in
-
-; church numerals
-let isZero = \n. n (\x. false) true in
-let succ = \n s z. s (n s z) in
-let mult = \m n s. m (n s) in
-let length = Y (\length l. isNil l 0 (succ (length (tail l)))) in
-let pred = \n. isZero n 0 (length (tail (n (\p. makeList meh p) nil)))
-in
-let leq = \m n. isZero(n pred m) in
-let eq = \m n. and (leq m n)(leq n m) in
-
-eq 2 2 yes no
-</pre>
+       ; booleans
+       let true = \x y. x in
+       let false = \x y. y in
+       let and = \l r. l (r true false) false in
+
+       ; version 1 lists
+       let make_pair = \f s g. g f s in
+       let fst = true in
+       let snd = false in
+       let empty = make_pair true junk in
+       let isempty = \x. x fst in
+       let make_list = \h t. make_pair false (make_pair h t) in
+       let head = \l. isempty l err (l snd fst) in
+       let tail = \l. isempty l err (l snd snd) in
+
+       ; a list of numbers to experiment on
+       let mylist = make_list 1 (make_list 2 (make_list 3 empty)) in
+
+       ; a fixed-point combinator for defining recursive functions
+       let Y = \f. (\h. f (h h)) (\h. f (h h)) in
+
+       ; church numerals
+       let iszero = \n. n (\x. false) true in
+       let succ = \n s z. s (n s z) in
+       let mult = \m n s. m (n s) in
+       let length = Y (\length l. isempty l 0 (succ (length (tail l)))) in
+       let pred = \n. iszero n 0 (length (tail (n (\p. make_list junk p) empty)))
+       in
+       let leq = \m n. iszero(n pred m) in
+       let eq = \m n. and (leq m n)(leq n m) in
+
+       eq 2 2 yes no
 
 
 Then `length mylist` evaluates to 3.
@@ -53,19 +51,14 @@ Then `length mylist` evaluates to 3.
 function, write a function that computes factorials.  (Recall that n!,
 the factorial of n, is n times the factorial of n-1.)
 
-Warning: my browser isn't able to compute factorials of numbers
-greater than 2 (it does't provide enough resources for the JavaScript
-interpreter; web pages are not supposed to be that computationally
-intensive).
+       Warning: it takes a long time for my browser to compute factorials larger than 4!
 
-3. (Easy) Write a function `listLenEq` that returns true just in case
-two lists have the
-same length.  That is,
+3. (Easy) Write a function `equal_length` that returns true just in case
+two lists have the same length.  That is,
 
-     listLenEq mylist (makeList meh (makeList meh (makeList meh nil)))
-     ~~> true
+               equal_length mylist (make_list junk (make_list junk (make_list junk empty))) ~~> true
 
-     listLenEq mylist (makeList meh (makeList meh nil))) ~~> false
+               equal_length mylist (make_list junk (make_list junk empty))) ~~> false
 
 
 4. (Still easy) Now write the same function, but don't use the length
@@ -82,18 +75,20 @@ lists.
 
 #Computing with trees#
 
-Linguists analyze natural language expressions into trees.  
+Linguists analyze natural language expressions into trees.
+
 We'll need trees in future weeks, and tree structures provide good
 opportunities for learning how to write recursive functions.
 Making use of the resources we have at the moment, we can approximate
 trees as follows: instead of words, we'll use Church numerals.
 Then a tree will be a (version 1 type) list in which each element is
-itself a tree.  For simplicity, we'll adopt the convention that 
-a tree of length 1 must contain a number as its only element.  
+itself a tree.  For simplicity, we'll adopt the convention that
+a tree of length 1 must contain a number as its only element.
+
 Then we have the following representations:
 
 <pre>
-   (a)           (b)             (c)  
+   (a)           (b)             (c)
     .
    /|\            /\              /\
   / | \          /\ 3            1 /\
@@ -113,29 +108,31 @@ whether the length of the list is less than or equal to 1.  This will
 be your base case for your recursive functions that operate on these
 trees.
 
-1.    Write a function that sums the number of leaves in a tree.
+<OL start=6>
+<LI>Write a function that sums the number of leaves in a tree.
 
 Expected behavior:
 
-<pre>
-let t1 = (makeList 1 nil) in
-let t2 = (makeList 2 nil) in
-let t3 = (makeList 3 nil) in
-let t12 = (makeList t1 (makeList t2 nil)) in
-let t23 = (makeList t2 (makeList t3 nil)) in
-let ta = (makeList t1 t23) in
-let tb = (makeList t12 t3) in
-let tc = (makeList t1 (makeList t23 nil)) in
-
-sum-leaves t1 ~~> 1
-sum-leaves t2 ~~> 2
-sum-leaves t3 ~~> 3
-sum-leaves t12 ~~> 3
-sum-leaves t23 ~~> 5
-sum-leaves ta ~~> 6
-sum-leaves tb ~~> 6
-sum-leaves tc ~~> 6
-</pre>
+       let t1 = (make_list 1 empty) in
+       let t2 = (make_list 2 empty) in
+       let t3 = (make_list 3 empty) in
+       let t12 = (make_list t1 (make_list t2 empty)) in
+       let t23 = (make_list t2 (make_list t3 empty)) in
+       let ta = (make_list t1 t23) in
+       let tb = (make_list t12 t3) in
+       let tc = (make_list t1 (make_list t23 empty)) in
+
+       sum-leaves t1 ~~> 1
+       sum-leaves t2 ~~> 2
+       sum-leaves t3 ~~> 3
+       sum-leaves t12 ~~> 3
+       sum-leaves t23 ~~> 5
+       sum-leaves ta ~~> 6
+       sum-leaves tb ~~> 6
+       sum-leaves tc ~~> 6
+
+
+<LI>Write a function that counts the number of leaves.
 
-2.   Write a function that counts the number of leaves.
+</OL>
 
diff --git a/assignment4.mdwn b/assignment4.mdwn
new file mode 100644 (file)
index 0000000..34fb044
--- /dev/null
@@ -0,0 +1,43 @@
+Assignment 4
+------------
+
+#Reversing a list#
+
+How would you define an operation to reverse a list? (Don't peek at the
+[[lambda_library]]! Try to figure it out on your own.) Choose whichever
+implementation of list you like. Even then, there are various strategies you
+can use.
+
+
+#Comparing lists for equality#
+
+<!--
+let list_equal =
+    \left right. left
+                ; here's our f
+                (\hd sofar.
+                    ; deconstruct our sofar-pair
+                    sofar (\might_be_equal right_tail.
+                        ; our new sofar
+                        make_pair
+                        (and (and might_be_equal (not (isempty right_tail))) (eq? hd (head right_tail)))
+                        (tail right_tail)
+                    ))
+                ; here's our z
+                ; we pass along the fold a pair
+                ; (might_for_all_i_know_still_be_equal?, tail_of_reversed_right)
+                ; when left is empty, the lists are equal if right is empty
+                (make_pair
+                    (not (isempty right))
+                    (reverse right)
+                )
+                ; when fold is finished, check sofar-pair
+                (\might_be_equal right_tail. and might_be_equal (isempty right_tail))
+-->
+
+#Enumerating the fringe of a leaf-labeled tree#
+
+[[Implementing trees]]
+
+
+
index ddf1e89..921040d 100644 (file)
@@ -88,6 +88,7 @@ String.prototype.tokens = function (prefix, suffix) {
                                // should only be terminal
                     str += c;
                     i += 1;
+                                       c = this.charAt(i);
                                // make sure next character is not an identifier
                                        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
                                                (c >= '0' && c <= '9') || c === '_' || c === '-' || c === '/' || c === '?' || c === '!') {
diff --git a/implementing_trees.mdwn b/implementing_trees.mdwn
new file mode 100644 (file)
index 0000000..31dc3fd
--- /dev/null
@@ -0,0 +1,194 @@
+#Implementing trees#
+
+In [[Assignment3]] we proposed a very ad-hoc-ish implementation of trees.
+
+Think about how you'd implement them in a more principled way. You could
+use any of the version 1 -- version 5 implementation of lists as a model.
+
+To keep things simple, we'll stick to binary trees. A node will either be a
+*leaf* of the tree, or it will have exactly two children.
+
+There are two kinds of trees to think about. In one sort of tree, it's only
+the tree's leaves that are labeled:
+
+               .
+          / \ 
+         .   3
+        / \
+       1   2 
+
+Linguists often use trees of this sort. The inner, non-leaf nodes of the
+tree do have associated values. But what values they are can be determined from
+the structure of the tree and the values of the node's left and right children.
+So the inner node doesn't need its own independent label.
+
+In another sort of tree, the tree's inner nodes are also labeled:
+
+               4
+          / \ 
+         2   5
+        / \
+       1   3 
+
+When you want to efficiently arrange an ordered collection, so that it's
+easy to do a binary search through it, this is the way you usually structure
+your data.
+
+These latter sorts of trees can helpfully be thought of as ones where
+*only* the inner nodes are labeled. Leaves can be thought of as special,
+dead-end branches with no label:
+
+                  .4.
+                 /   \ 
+                2     5
+               / \   / \
+          1   3  x x
+         / \ / \
+        x  x x  x
+
+In our earlier discussion of lists, we said they could be thought of as
+data structures of the form:
+
+       Empty_list | Non_empty_list (its_head, its_tail)
+
+And that could in turn be implemented in v2 form as:
+
+       the_list (\head tail. non_empty_handler) empty_handler
+
+Similarly, the leaf-labeled tree:
+
+               .
+          / \ 
+         .   3
+        / \
+       1   2 
+
+can be thought of as a data structure of the form:
+
+       Leaf (its_label) | Non_leaf (its_left_subtree, its_right_subtree)
+
+and that could be implemented in v2 form as:
+
+       the_tree (\left right. non_leaf_handler) (\label. leaf_handler)
+
+And the node-labeled tree:
+
+                  .4.
+                 /   \ 
+                2     5
+               / \   / \
+          1   3  x x
+         / \ / \
+        x  x x  x
+
+can be thought of as a data structure of the form:
+
+       Leaf | Non_leaf (its_left_subtree, its_label, its_right_subtree)
+
+and that could be implemented in v2 form as:
+
+       the_tree (\left label right. non_leaf_handler) leaf_result
+
+
+What would correspond to "folding" a function `f` and base value `z` over a
+tree? Well, if it's an empty tree:
+
+       x
+
+we should presumably get back `z`. And if it's a simple, non-empty tree:
+
+         1
+        / \
+       x   x
+
+we should expect something like `f z 1 z`, or `f <result of folding f and z
+over left subtree> label_of_this_node <result of folding f and z over right
+subtree>`. (It's not important what order we say `f` has to take its arguments
+in.)
+
+A v3-style implementation of node-labeled trees, then, might be:
+
+       let empty_tree = \f z. z  in
+       let make_tree = \left label right. \f z. f (left f z) label (right f z)  in
+       ...
+
+Think about how you might implement other tree operations, such as getting
+the label of the root (topmost node) of a tree; extracting the left subtree of
+a node; and so on.
+
+Think about different ways you might implement leaf-labeled trees.
+
+If you had one tree and wanted to make a larger tree out of it, adding in a
+new element, how would you do that?
+
+When using trees to represent linguistic structures, one doesn't have
+latitude about *how* to build a larger tree. The linguistic structure you're
+trying to represent will determine where the new element should be placed, and
+where the previous tree should be placed.
+
+However, when using trees as a computational tool, one usually does have
+latitude about how to structure a larger tree---in the same way that we had the
+freedom to implement our sets with lists whose members were just appended in
+the order we built the set up, or instead with lists whose members were ordered
+numerically.
+
+When building a new tree, one strategy for where to put the new element and
+where to put the existing tree would be to always lean towards a certain side.
+For instance, to add the element `2` to the tree:
+
+         1
+        / \
+       x   x
+
+we might construct the following tree:
+
+         1
+        / \
+       x   2
+          / \
+         x   x
+
+or perhaps we'd do it like this instead:
+
+         2
+        / \
+       x   1
+          / \
+         x   x
+
+However, if we always leaned to the right side in this way, then the tree
+would get deeper and deeper on that side, but never on the left:
+
+         1
+        / \
+       x   2
+          / \
+         x   3
+                / \
+               x   4
+                  / \
+                 x   5
+                        / \
+                       x   x
+
+and that wouldn't be so useful if you were using the tree as an arrangement
+to enable *binary searches* over the elements it holds. For that, you'd prefer
+the tree to be relatively "balanced", like this:
+
+                  .4.
+                 /   \ 
+                2     5
+               / \   / \
+          1   3  x x
+         / \ / \
+        x  x x  x
+
+Do you have any ideas about how you might efficiently keep the new trees
+you're building pretty "balanced" in this way?
+
+This is a large topic in computer science. There's no need for you to learn
+the various strategies that they've developed for doing this. But
+thinking in broad brush-strokes about what strategies might be promising will
+help strengthen your understanding of trees, and useful ways to implement them
+in a purely functional setting like the lambda calculus.
+
index d0d77f7..c4f7dc6 100644 (file)
@@ -7,15 +7,66 @@ This course will be co-taught by [Chris Barker](http://homepages.nyu.edu/~cb125/
 
 ## Announcements ##
 
-The seminar meets on Mondays from 4-6, in 
+*      The seminar meets on Mondays from 4-6, in 
 the Linguistics building at 10 Washington Place, in room 104 (back of the first floor).
 
-Student sessions will be held on Tuesdays from 11-12 and Wednesdays from 3-4. (You only need attend one session.) You should see these sessions as opportunities to clear up lingering issues from material we've discussed, and help get a better footing for what we'll be doing the next week. It would be smart to make a serious start on that week's homework, for instance, before the session.
+*      One student session will be held every Wednesday from 3-4. The other will
+be arranged to fit the schedule of those who'd like to attend but can't
+make the Wednesday time. (We first proposed Tuesdays from 11-12, but this
+time turns out not to be so helpful.) If you're one of the students who
+wants to meet for Q&A at some other time in the week, let us know.
+
+       You should see the student sessions as opportunities to clear up lingering
+issues from material we've discussed, and help get a better footing for what
+we'll be doing the next week. It would be smart to make a serious start on that
+week's homework, for instance, before the session.
+
+*      There is now a [[lambda evaluator]] you can use in your browser (no need to
+install any software). It can help you check whether your answer to some of the
+homework questions works correctly.
+
+       There is also now a [library](/lambda_library) of lambda-calculus
+arithmetical and list operations, some relatively advanced.
+
+*      Henceforth, unless we say otherwise, every homework will be "due" by
+Sunday morning after the Monday seminar in which we refer to it.
+(Usually we'll post the assignment shortly before the seminar, but don't
+rely on this.) However, for every assignment there will be a "grace
+period" of one further week for you to continue working on it if you
+have trouble and aren't able to complete the assignment to your
+satisfaction by the due date. You shouldn't hesitate to talk to us---or
+each other!---about the assignments when you do have trouble. We don't
+mind so much if you come across answers to the assignment when browsing
+the web, or the Little Schemer book, or anywhere. So long as you can
+reason yourself through the solutions and experience for yourself the
+insights they embody.
+
+       We reserve the privilege to ruthlessly require you to
+explain your solutions in conversations at any point, in section or in
+class.
+
+       You should always *aim* to complete the assignments by the "due" date,
+as this will fit best with the progress of the seminar. Let's take
+assignment 3 to be "due" on Sunday Oct 3 (the date of this
+announcement), but as we announced last week in seminar, you can take up
+until this coming Sunday to complete it. If you need to. Try to complete
+it, and get assistance completing it if you need it, sooner.
+
+*      We'll shortly be posting another assignment, assignment 4, which will be
+"due" on the Sunday before our next seminar. That is, on Sunday Oct 17.
+(There's no seminar this coming Monday.)
+
+       The assignments will tend to be quite challenging. Again, you should by
+all means talk amongst yourselves, and to us, about strategies and
+questions that come up when working through them.
+
+       We will not always be able to predict accurately which problems are
+easy and which are hard.  If we misjudge, and choose a problem that is
+too hard for you to complete to your own satisfaction, it is still
+very much worthwhile (and very much appreciated) if you would explain
+what is difficult, what you tried, why what you tried didn't work, and
+what you think you need in order to solve the problem.
 
-We've sent around an email to those who left their email addresses on the roster we passed around. But it's clear that the roster didn't make its way to everyone. So if you're not receiving our seminar emails, please email <mailto:jim.pryor@nyu.edu> with your email address, and if you're a student, say whether you expect to audit or take the class for credit.
-
-There is now a [[lambda evaluator]] you can use in your browser (no need to install any software).
-It can help you check whether your answer to some of the homework questions works correctly.
 
 <!--
   To play around with a **typed lambda calculus**, which we'll look at later
@@ -36,10 +87,12 @@ Topics: Applications; Basics of Lambda Calculus; Comparing Different Languages
 
 Topics: Reduction and Convertibility; Combinators; Evaluation Strategies and Normalization; Decidability; Lists and Numbers
 
-(27 Sept) Lecture notesfor [[Week3]];  [[Assignment3]].
+(27 Sept) Lecture notes for [[Week3]];  [[Assignment3]].
 
 Topics: Recursion with Fixed Point Combinators
 
+(4 Oct) Lecture notes for Week 4
+
 <!-- Introducing the notion of a "continuation", which technique we'll now already have used a few times
 -->
 
similarity index 83%
rename from arithmetic.mdwn
rename to lambda_library.mdwn
index fafecaf..ff36375 100644 (file)
@@ -1,5 +1,17 @@
 Here are a bunch of pre-tested operations for the untyped lambda calculus. In some cases multiple versions are offered.
 
+Some of these are drawn from:
+
+*      [[!wikipedia Lambda calculus]]
+*      [[!wikipedia Church encoding]]
+*      Oleg's [Basic Lambda Calculus Terms](http://okmij.org/ftp/Computation/lambda-calc.html#basic)
+
+and all sorts of other places. Others of them are our own handiwork.
+
+
+**Spoilers!** Below you'll find implementations of map and filter for v3 lists, and several implementations of leq for Church numerals. Those were all requested in Assignment 2; so if you haven't done that yet, you should try to figure them out on your own. (You can find implementations of these all over the internet, if you look for them, so these are no great secret. In fact, we'll be delighted if you're interested enough in the problem to try to think through alternative implementations.)
+
+
        ; booleans
        let true = \y n. y  in ; aka K
        let false = \y n. n  in ; aka K I
@@ -53,7 +65,7 @@ Here are a bunch of pre-tested operations for the untyped lambda calculus. In so
        ; more efficient reverse builds a left-fold instead
        ; (make_left_list a (make_left_list b (make_left_list c empty)) ~~> \f z. f c (f b (f a z))
        let reverse = (\make_left_list lst. lst make_left_list empty) (\h t f z. t f (f h z))  in
-       ; zip [a;b;c] [x; y; z] ~~> [(a,x);(b,y);(c,z)]
+       ; zip [a;b;c] [x;y;z] ~~> [(a,x);(b,y);(c,z)]
        let zip = \left right. (\base build. reverse left build base (\x y. reverse x))
                        ; where base is
                        (make_pair empty (map (\h u. u h) right))
@@ -228,6 +240,10 @@ Here are a bunch of pre-tested operations for the untyped lambda calculus. In so
        let length = Y (\self lst. isempty lst 0 (succ (self (tail lst))))  in
 
 
+       true
+
+<!--
+
        ; numhelper 0 f z ~~> z
        ; when n > 0: numhelper n f z ~~> f (pred n)
        ; compare Bunder/Urbanek pred
@@ -239,7 +255,7 @@ Here are a bunch of pre-tested operations for the untyped lambda calculus. In so
 
 
        fact Theta 3  ; returns 6
-
+-->
 
 <!--
        ; my original efficient comparisons
@@ -276,6 +292,32 @@ Here are a bunch of pre-tested operations for the untyped lambda calculus. In so
 
                negate_int = \int. sign_case int (church_to_negint (abs int)) zero (church_to_int (abs int))
 
-       for more, see http://okmij.org/ftp/Computation/lambda-arithm-neg.scm
+       for more, see http://okmij.org/ftp/Computation/lambda-calc.html#neg
+
 
 -->
+
+<!--
+let list_equal =
+    \left right. left
+                ; here's our f
+                (\hd sofar.
+                    ; deconstruct our sofar-pair
+                    sofar (\might_be_equal right_tail.
+                        ; our new sofar
+                        make_pair
+                        (and (and might_be_equal (not (isempty right_tail))) (eq? hd (head right_tail)))
+                        (tail right_tail)
+                    ))
+                ; here's our z
+                ; we pass along the fold a pair
+                ; (might_for_all_i_know_still_be_equal?, tail_of_reversed_right)
+                ; when left is empty, the lists are equal if right is empty
+                (make_pair
+                    (not (isempty right))
+                    (reverse right)
+                )
+                ; when fold is finished, check sofar-pair
+                (\might_be_equal right_tail. and might_be_equal (isempty right_tail))
+-->
+
diff --git a/new_stuff.mdwn b/new_stuff.mdwn
new file mode 100644 (file)
index 0000000..f39287d
--- /dev/null
@@ -0,0 +1,12 @@
+Page for Chris and Jim to see what each other is working on, but hasn't necessarily been posted to main wiki index yet.
+
+[[Week4]]
+
+[[Week5]]
+
+[[Assignment4]]
+
+[[advanced_lambda]]
+
+[[lambda_library]]
+
index d46981e..78e2d37 100644 (file)
@@ -6,75 +6,80 @@ A: That's easy: let `T` be an arbitrary term in the lambda calculus.  If
 `T` has a fixed point, then there exists some `X` such that `X <~~>
 TX` (that's what it means to *have* a fixed point).
 
-<pre>
-let W = \x.T(xx) in
-let X = WW in
-X = WW = (\x.T(xx))W = T(WW) = TX
-</pre>
+<pre><code>let L = \x. T (x x) in
+let X = L L in
+X &equiv; L L &equiv; (\x. T (x x)) L ~~> T (L L) &equiv; T X
+</code></pre>
 
 Please slow down and make sure that you understand what justified each
 of the equalities in the last line.
 
-#Q: How do you know that for any term `T`, `YT` is a fixed point of `T`?#
+#Q: How do you know that for any term `T`, `Y T` is a fixed point of `T`?#
 
 A: Note that in the proof given in the previous answer, we chose `T`
-and then set `X = WW = (\x.T(xx))(\x.T(xx))`.  If we abstract over
-`T`, we get the Y combinator, `\T.(\x.T(xx))(\x.T(xx))`.  No matter
-what argument `T` we feed Y, it returns some `X` that is a fixed point
+and then set <code>X &equiv; L L &equiv; (\x. T (x x)) (\x. T (x x))</code>.  If we abstract over
+`T`, we get the Y combinator, `\T. (\x. T (x x)) (\x. T (x x))`.  No matter
+what argument `T` we feed `Y`, it returns some `X` that is a fixed point
 of `T`, by the reasoning in the previous answer.
 
 #Q: So if every term has a fixed point, even `Y` has fixed point.#
 
 A: Right:
 
-    let Y = \T.(\x.T(xx))(\x.T(xx)) in
-    Y Y = \T.(\x.T(xx))(\x.T(xx)) Y
-        = (\x.Y(xx))(\x.Y(xx))
-        = Y((\x.Y(xx))(\x.Y(xx)))
-        = Y(Y((\x.Y(xx))(\x.Y(xx))))
-        = Y(Y(Y(...(Y(YY))...)))
+<pre><code>let Y = \T. (\x. T (x x)) (\x. T (x x)) in
+Y Y &equiv; \T. (\x. T (x x)) (\x. T (x x)) Y
+~~> (\x. Y (x x)) (\x. Y (x x))
+~~> Y ((\x. Y (x x)) (\x. Y (x x)))
+~~> Y (Y ((\x. Y (x x)) (\x. Y (x x))))
+~~> Y (Y (Y (...(Y (Y Y))...)))</code></pre>
+
 
 #Q: Ouch!  Stop hurting my brain.#
 
-A: Let's come at it from the direction of arithmetic.  Recall that we
+A: Is that a question?
+
+Let's come at it from the direction of arithmetic.  Recall that we
 claimed that even `succ`---the function that added one to any
 number---had a fixed point.  How could there be an X such that X = X+1?
 That would imply that
 
-    X = succ X = succ (succ X) = succ (succ (succ (X))) = succ (... (succ X)...)
+    X <~~> succ X <~~> succ (succ X) <~~> succ (succ (succ X)) <~~> succ (... (succ X)...)
 
 In other words, the fixed point of `succ` is a term that is its own
 successor.  Let's just check that `X = succ X`:
 
-    let succ = \n s z. s (n s z) in
-    let X = (\x.succ(xx))(\x.succ(xx)) in
-    succ X 
-      = succ ((\x.succ(xx))(\x.succ(xx))) 
-      = succ (succ ((\x.succ(xx))(\x.succ(xx))))
-      = succ (succ X)
+<pre><code>let succ = \n s z. s (n s z) in
+let X = (\x. succ (x x)) (\x. succ (x x)) in
+succ X 
+&equiv; succ ( (\x. succ (x x)) (\x. succ (x x)) ) 
+~~> succ (succ ( (\x. succ (x x)) (\x. succ (x x)) ))
+&equiv; succ (succ X)
+</code></pre>
+
+You should see the close similarity with `Y Y` here.
 
-You should see the close similarity with YY here.
 
 #Q. So `Y` applied to `succ` returns a number that is not finite!#
 
 A. Yes!  Let's see why it makes sense to think of `Y succ` as a Church
 numeral:
 
-      [same definitions]
-      succ X
-      = (\n s z. s (n s z)) X 
-      = \s z. s (X s z)
-      = succ (\s z. s (X s z)) ; using fixed-point reasoning
-      = \s z. s ([succ (\s z. s (X s z))] s z)
-      = \s z. s ([\s z. s ([succ (\s z. s (X s z))] s z)] s z)
-      = \s z. s (s (succ (\s z. s (X s z))))
+<pre><code>[same definitions]
+succ X
+&equiv; (\n s z. s (n s z)) X 
+~~> \s z. s (X s z)
+<~~> succ (\s z. s (X s z)) ; using fixed-point reasoning
+&equiv; (\n s z. s (n s z)) (\s z. s (X s z))
+~~> \s z. s ((\s z. s (X s z)) s z)
+~~> \s z. s (s (X s z))
+</code></pre>
 
 So `succ X` looks like a numeral: it takes two arguments, `s` and `z`,
 and returns a sequence of nested applications of `s`...
 
 You should be able to prove that `add 2 (Y succ) <~~> Y succ`,
-likewise for `mult`, `minus`, `pow`.  What happens if we try `minus (Y
-succ)(Y succ)`?  What would you expect infinity minus infinity to be?
+likewise for `mul`, `sub`, `pow`.  What happens if we try `sub (Y
+succ) (Y succ)`?  What would you expect infinity minus infinity to be?
 (Hint: choose your evaluation strategy so that you add two `s`s to the
 first number for every `s` that you add to the second number.)
 
@@ -84,13 +89,14 @@ represents arithmetic infinity.
 It's important to bear in mind the simplest term in question is not
 infinite:
 
-     Y succ = (\f.(\x.f(xx))(\x.f(xx)))(\n s z. s (n s z))
+       Y succ = (\f. (\x. f (x x)) (\x. f (x x))) (\n s z. s (n s z))
 
 The way that infinity enters into the picture is that this term has
 no normal form: no matter how many times we perform beta reduction,
 there will always be an opportunity for more beta reduction.  (Lather,
 rinse, repeat!)
 
+
 #Q. That reminds me, what about [[evaluation order]]?#
 
 A. For a recursive function that has a well-behaved base case, such as
@@ -100,63 +106,63 @@ which we have to make a choice about which beta reduction to perform
 next: one choice leads to a normal form, the other choice leads to
 endless reduction:
 
-    let prefac = \f n. isZero n 1 (mult n (f (pred n))) in
-    let fac = Y prefac in
-    fac 2
-       = [(\f.(\x.f(xx))(\x.f(xx))) prefac] 2
-       = [(\x.prefac(xx))(\x.prefac(xx))] 2
-       = [prefac((\x.prefac(xx))(\x.prefac(xx)))] 2
-       = [prefac(prefac((\x.prefac(xx))(\x.prefac(xx))))] 2
-       = [(\f n. isZero n 1 (mult n (f (pred n))))
-          (prefac((\x.prefac(xx))(\x.prefac(xx))))] 2
-       = [\n. isZero n 1 (mult n ([prefac((\x.prefac(xx))(\x.prefac(xx)))] (pred n)))] 2
-       = isZero 2 1 (mult 2 ([prefac((\x.prefac(xx))(\x.prefac(xx)))] (pred 2)))
-       = mult 2 ([prefac((\x.prefac(xx))(\x.prefac(xx)))] 1)
-       ...
-       = mult 2 (mult 1 ([prefac((\x.prefac(xx))(\x.prefac(xx)))] 0))
-       = mult 2 (mult 1 (isZero 0 1 ([prefac((\x.prefac(xx))(\x.prefac(xx)))] (pred 0))))
-       = mult 2 (mult 1 1)
-       = mult 2 1
-       = 2
+<pre><code>let prefact = \f n. iszero n 1 (mul n (f (pred n))) in
+let fact = Y prefact in
+fact 2
+&equiv; [(\f. (\x. f (x x)) (\x. f (x x))) prefact] 2
+~~> [(\x. prefact (x x)) (\x. prefact (x x))] 2
+~~> [prefact ((\x. prefact (x x)) (\x. prefact (x x)))] 2
+~~> [prefact (prefact ((\x. prefact (x x)) (\x. prefact (x x))))] 2
+&equiv; [ (\f n. iszero n 1 (mul n (f (pred n)))) (prefact ((\x. prefact (x x)) (\x. prefact (x x))))] 2
+~~> [\n. iszero n 1 (mul n ([prefact ((\x. prefact (x x)) (\x. prefact (x x)))] (pred n)))] 2
+~~> iszero 2 1 (mul 2 ([prefact ((\x. prefact (x x)) (\x. prefact (x x)))] (pred 2)))
+~~> mul 2 ([prefact ((\x. prefact (x x)) (\x. prefact (x x)))] 1)
+...
+~~> mul 2 (mul 1 ([prefact ((\x. prefact (x x)) (\x. prefact (x x)))] 0))
+&equiv; mul 2 (mul 1 (iszero 0 1 ([prefact ((\x. prefact (x x)) (\x. prefact (x x)))] (pred 0))))
+~~> mul 2 (mul 1 1)
+~~> mul 2 1
+~~> 2
+</code></pre>
 
 The crucial step is the third from the last.  We have our choice of
-either evaluating the test `isZero 0 1 ...`, which evaluates to `1`,
+either evaluating the test `iszero 0 1 ...`, which evaluates to `1`,
 no matter what the ... contains;
-or we can evaluate the `Y` pump, `(\x.prefac(xx))(\x.prefac(xx))`, to
-produce another copy of `prefac`.  If we postpone evaluting the
-`isZero` test, we'll pump out copy after copy of `prefac`, and never
+or we can evaluate the `Y` pump, `(\x. prefact (x x)) (\x. prefact (x x))`, to
+produce another copy of `prefact`.  If we postpone evaluting the
+`iszero` test, we'll pump out copy after copy of `prefact`, and never
 realize that we've bottomed out in the recursion.  But if we adopt a
 leftmost/call-by-name/normal-order evaluation strategy, we'll always
-start with the `isZero` predicate, and only produce a fresh copy of
-`prefac` if we are forced to. 
+start with the `iszero` predicate, and only produce a fresh copy of
+`prefact` if we are forced to. 
+
 
 #Q.  You claimed that the Ackerman function couldn't be implemented using our primitive recursion techniques (such as the techniques that allow us to define addition and multiplication).  But you haven't shown that it is possible to define the Ackerman function using full recursion.#
 
+
 A. OK:
   
-<pre>
-A(m,n) =
-    | when m == 0 -> n + 1
-    | else when n == 0 -> A(m-1,1)
-    | else -> A(m-1, A(m,n-1))
-
-let A = Y (\A m n. isZero m (succ n) (isZero n (A (pred m) 1) (A (pred m) (A m (pred n))))) in
-</pre>
-
-For instance,
-
-    A 1 2
-    = A 0 (A 1 1)
-    = A 0 (A 0 (A 1 0))
-    = A 0 (A 0 (A 0 1))
-    = A 0 (A 0 2)
-    = A 0 3
-    = 4
-
-A 1 x is to A 0 x as addition is to the successor function;
-A 2 x is to A 1 x as multiplication is to addition;
-A 3 x is to A 2 x as exponentiation is to multiplication---
-so A 4 x is to A 3 x as hyper-exponentiation is to exponentiation...
+       A(m,n) =
+               | when m == 0 -> n + 1
+               | else when n == 0 -> A(m-1,1)
+               | else -> A(m-1, A(m,n-1))
+
+       let A = Y (\A m n. iszero m (succ n) (iszero n (A (pred m) 1) (A (pred m) (A m (pred n)))))
+
+So for instance:
+
+       A 1 2
+       ~~> A 0 (A 1 1)
+       ~~> A 0 (A 0 (A 1 0))
+       ~~> A 0 (A 0 (A 0 1))
+       ~~> A 0 (A 0 2)
+       ~~> A 0 3
+       ~~> 4
+
+`A 1 x` is to `A 0 x` as addition is to the successor function;
+`A 2 x` is to `A 1 x` as multiplication is to addition;
+`A 3 x` is to `A 2 x` as exponentiation is to multiplication---
+so `A 4 x` is to `A 3 x` as hyper-exponentiation is to exponentiation...
 
 #Q. What other questions should I be asking?#
 
@@ -173,129 +179,398 @@ so A 4 x is to A 3 x as hyper-exponentiation is to exponentiation...
      Is leastness important?
 
 
-##The simply-typed lambda calculus##
-
-The uptyped lambda calculus is pure computation.  It is much more
-common, however, for practical programming languages to be typed.
-Likewise, systems used to investigate philosophical or linguistic
-issues are almost always typed.  Types will help us reason about our
-computations.  They will also facilitate a connection between logic
-and computation.
-
-Soon we will consider polymorphic type systems.  First, however, we
-will consider the simply-typed lambda calculus.  There's good news and
-bad news: the good news is that the simply-type lambda calculus is
-strongly normalizing: every term has a normal form.  We shall see that
-self-application is outlawed, so &Omega; can't even be written, let
-alone undergo reduction.  The bad news is that fixed-point combinators
-are also forbidden, so recursion is neither simple nor direct.
 
-#Types#
+#Sets#
+
+You're now already in a position to implement sets: that is, collections with
+no intrinsic order where elements can occur at most once. Like lists, we'll
+understand the basic set structures to be *type-homogenous*. So you might have
+a set of integers, or you might have a set of pairs of integers, but you
+wouldn't have a set that mixed both types of elements. Something *like* the
+last option is also achievable, but it's more difficult, and we won't pursue it
+now. In fact, we won't talk about sets of pairs, either. We'll just talk about
+sets of integers. The same techniques we discuss here could also be applied to
+sets of pairs of integers, or sets of triples of booleans, or sets of pairs
+whose first elements are booleans, and whose second elements are triples of
+integers. And so on.
+
+(You're also now in a position to implement *multi*sets: that is, collections
+with no intrinsic order where elements can occur multiple times: the multiset
+{a,a} is distinct from the multiset {a}. But we'll leave these as an exercise.)
+
+The easiest way to implement sets of integers would just be to use lists. When
+you "add" a member to a set, you'd get back a list that was either identical to
+the original list, if the added member already was present in it, or consisted
+of a new list with the added member prepended to the old list. That is:
+
+       let empty_set = empty  in
+       ; see the library for definitions of any and eq
+       let make_set = \new_member old_set. any (eq new_member) old_set
+                                               ; if any element in old_set was eq new_member
+                                               old_set
+                                               ; else
+                                               make_list new_member old_set
+
+Think about how you'd implement operations like `set_union`,
+`set_intersection`, and `set_difference` with this implementation of sets.
+
+The implementation just described works, and it's the simplest to code.
+However, it's pretty inefficient. If you had a 100-member set, and you wanted
+to create a set which had all those 100-members and some possibly new element
+`e`, you might need to check all 100 members to see if they're equal to `e`
+before concluding they're not, and returning the new list. And comparing for
+numeric equality is a moderately expensive operation, in the first place.
+
+(You might say, well, what's the harm in just prepending `e` to the list even
+if it already occurs later in the list. The answer is, if you don't keep track
+of things like this, it will likely mess up your implementations of
+`set_difference` and so on. You'll have to do the book-keeping for duplicates
+at some point in your code. It goes much more smoothly if you plan this from
+the very beginning.)
+
+How might we make the implementation more efficient? Well, the *semantics* of
+sets says that they have no intrinsic order. That means, there's no difference
+between the set {a,b} and the set {b,a}; whereas there is a difference between
+the *list* `[a;b]` and the list `[b;a]`. But this semantic point can be respected
+even if we *implement* sets with something ordered, like list---as we're
+already doing. And we might *exploit* the intrinsic order of lists to make our
+implementation of sets more efficient.
+
+What we could do is arrange it so that a list that implements a set always
+keeps in elements in some specified order. To do this, there'd have *to be*
+some way to order its elements. Since we're talking now about sets of numbers,
+that's easy. (If we were talking about sets of pairs of numbers, we'd use
+"lexicographic" ordering, where `(a,b) < (c,d)` iff `a < c or (a == c and b <
+d)`.)
+
+So, if we were searching the list that implements some set to see if the number
+`5` belonged to it, once we get to elements in the list that are larger than `5`,
+we can stop. If we haven't found `5` already, we know it's not in the rest of the
+list either.
+
+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
+implements the set, if it belonged to the set at all. Or that it fell in the
+right half, it it belonged to the set at all. And then the same sort of
+determination could be made for whichever half you were directed to. And then
+for whichever quarter you were directed to next. And so on. Until you either
+found the element or exhausted the structure and could then conclude that the
+element in question was not part of the set. These sorts of structures are done
+using **binary trees** (see below).
+
+
+#Aborting a search through a list#
+
+We said that the sorted-list implementation of a set was more efficient than
+the unsorted-list implementation, because as you were searching through the
+list, you could come to a point where you knew the element wasn't going to be
+found. So you wouldn't have to continue the search.
+
+If your implementation of lists was, say v1 lists plus the Y-combinator, then
+this is exactly right. When you get to a point where you know the answer, you
+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
+contains the number `3`. The expression which represents this search would have
+something like the following form:
+
+       ..................<eq? 1 3>  ~~>
+       .................. false     ~~>
+       .............<eq? 2 3>       ~~>
+       ............. false          ~~>
+       .........<eq? 3 3>           ~~>
+       ......... true               ~~>
+       ?
+
+Of course, whether those reductions actually followed in that order would
+depend on what reduction strategy was in place. But the result of folding the
+search function over the part of the list whose head is `3` and whose tail is `[2;
+1]` will *semantically* depend on the result of applying that function to the
+more rightmost pieces of the list, too, regardless of what order the reduction
+is computed by. Conceptually, it will be easiest if we think of the reduction
+happening in the order displayed above.
+
+Well, once we've found a match between our sought number `3` and some member of
+the list, we'd like to avoid any further unnecessary computations and just
+deliver the answer `true` as "quickly" or directly as possible to the larger
+computation in which the search was embedded.
+
+With a Y-combinator based search, as we said, we could do this by just not
+following a recursion branch.
+
+But with the v3 lists, the fold is "pre-programmed" to continue over the whole
+list. There is no way for us to bail out of applying the search function to the
+parts of the list that have head `4` and head `5`, too.
 
-We will have at least one ground type, `o`.  From a linguistic point
-of view, think of the ground types as the bar-level 0 categories, that
-is, the lexical types, such as Noun, Verb, Preposition (glossing over
-the internal complexity of those categories in modern theories).
+We *can* avoid *some* unneccessary computation. The search function can detect
+that the result we've accumulated so far during the fold is now `true`, so we
+don't need to bother comparing `4` or `5` to `3` for equality. That will simplify the
+computation to some degree, since as we said, numerical comparison in the
+system we're working in is moderately expensive.
+
+However, we're still going to have to traverse the remainder of the list. That
+`true` result will have to be passed along all the way to the leftmost head of
+the list. Only then can we deliver it to the larger computation in which the
+search was embedded.
+
+It would be better if there were some way to "abort" the list traversal. If,
+having found the element we're looking for (or having determined that the
+element isn't going to be found), we could just immediately stop traversing the
+list with our answer. **Continuations** will turn out to let us do that.
 
-In addition, there will be a recursively-defined class of complex
-types `T`, the smallest set such that
+We won't try yet to fully exploit the terrible power of continuations. But
+there's a way that we can gain their benefits here locally, without yet having
+a fully general machinery or understanding of what's going on.
 
-*    ground types, including `o`, are in `T`
+The key is to recall how our implementations of booleans and pairs worked.
+Remember that with pairs, we supply the pair "handler" to the pair as *an
+argument*, rather than the other way around:
 
-*    for any types &sigma; and &tau; in `T`, the type &sigma; -->
-     &tau; is in `T`.
+       pair (\x y. add x y)
 
-For instance, here are some types in `T`:
+or:
 
-     o
-     o --> o
-     o --> o --> o
-     (o --> o) --> o
-     (o --> o) --> o --> o
+       pair (\x y. x)
 
-and so on.
+to get the first element of the pair. Of course you can lift that if you want:
 
-#Typed lambda terms#
+<pre><code>extract_fst &equiv; \pair. pair (\x y. x)</code></pre>
 
-Given a set of types `T`, we define the set of typed lambda terms <code>&Lambda;_T</code>,
-which is the smallest set such that
+but at a lower level, the pair is still accepting its handler as an argument,
+rather than the handler taking the pair as an argument. (The handler gets *the
+pair's elements*, not the pair itself, as arguments.)
 
-*    each type `t` has an infinite set of distinct variables, {x^t}_1,
-     {x^t}_2, {x^t}_3, ...
+>      *Terminology*: we'll try to use names of the form `get_foo` for handlers, and
+names of the form `extract_foo` for lifted versions of them, that accept the
+lists (or whatever data structure we're working with) as arguments. But we may
+sometimes forget.
 
-*    If a term `M` has type &sigma; --> &tau;, and a term `N` has type
-     &sigma;, then the application `(M N)` has type &tau;.
+The v2 implementation of lists followed a similar strategy:
 
-*    If a variable `a` has type &sigma;, and term `M` has type &tau;, 
-     then the abstract <code>&lambda; a M</code> has type &sigma; --> &tau;.
+       v2list (\h t. do_something_with_h_and_t) result_if_empty
 
-The definitions of types and of typed terms should be highly familiar
-to semanticists, except that instead of writing &sigma; --> &tau;,
-linguists (following Montague, who followed Church) write <&sigma;,
-&tau;>.  We will use the arrow notation, since it is more iconic.
+If the `v2list` here is not empty, then this will reduce to the result of
+supplying the list's head and tail to the handler `(\h t.
+do_something_with_h_and_t)`.
 
-Some examples (assume that `x` has type `o`):
+Now, what we've been imagining ourselves doing with the search through the v3
+list is something like this:
 
-      x            o
-      \x.x         o --> o
-      ((\x.x) x)   o
 
-Excercise: write down terms that have the following types:
+       larger_computation (search_through_the_list_for_3) other_arguments
 
-                   o --> o --> o
-                   (o --> o) --> o --> o
-                   (o --> o --> o) --> o
+That is, the result of our search is supplied as an argument (perhaps together
+with other arguments) to the "larger computation". Without knowing the
+evaluation order/reduction strategy, we can't say whether the search is
+evaluated before or after it's substituted into the larger computation. But
+semantically, the search is the argument and the larger computation is the
+function to which it's supplied.
 
-#Associativity of types versus terms#
+What if, instead, we did the same kind of thing we did with pairs and v2
+lists? That is, what if we made the larger computation a "handler" that we
+passed as an argument to the search?
 
-As we have seen many times, in the lambda calculus, function
-application is left associative, so that `f x y z == (((f x) y) z)`.
-Types, *THEREFORE*, are right associative: if `f`, `x`, `y`, and `z`
-have types `a`, `b`, `c`, and `d`, respectively, then `f` has type `a
---> b --> c --> d == (a --> (b --> (c --> d)))`.
+       the_search (\search_result. larger_computation search_result other_arguments)
 
-It is a serious faux pas to associate to the left for types.  You may
-as well use your salad fork to stir your tea.
+What's the advantage of that, you say. Other than to show off how cleverly
+you can lift.
 
-#The simply-typed lambda calculus is strongly normalizing#
+Well, think about it. Think about the difficulty we were having aborting the
+search. Does this switch-around offer us anything useful?
 
-If `M` is a term with type &tau; in &Lambda;_T, then `M` has a
-normal form.  The proof is not particularly complex, but we will not
-present it here; see Berendregt or Hankin.
+It could.
 
-Since &Omega; does not have a normal form, it follows that &Omega;
-cannot have a type in &Lambda;_T.  We can easily see why:
+What if the way we implemented the search procedure looked something like this?
 
-     &Omega; = (\x.xx)(\x.xx)
+At a given stage in the search, we wouldn't just apply some function `f` to the
+head at this stage and the result accumulated so far (from folding the same
+function, and a base value, to the tail at this stage)...and then pass the result
+of that application to the embedding, more leftward computation.
 
-Assume &Omega; has type &tau;, and `\x.xx` has type &sigma;.  Then
-because `\x.xx` takes an argument of type &sigma; and returns
-something of type &tau;, `\x.xx` must also have type &sigma; -->
-&tau;.  By repeating this reasoning, `\x.xx` must also have type
-(&sigma; --> &tau;) --> &tau;; and so on.  Since variables have
-finite types, there is no way to choose a type for the variable `x`
-that can satisfy all of the requirements imposed on it.
+We'd *instead* give `f` a "handler" that expects the result of the current
+stage *as an argument*, and then evaluates to what you'd get by passing that
+result leftwards up the list, as before. 
 
-In general, there is no way for a function to have a type that can
-take itself for an argument.  It follows that there is no way to
-define the identity function in such a way that it can take itself as
-an argument.  Instead, there must be many different identity
-functions, one for each type.
+Why would we do that, you say? Just more flamboyant lifting?
 
-#Typing numerals#
+Well, no, there's a real point here. If we give the function a "handler" that
+encodes the normal continuation of the fold leftwards through the list, we can
+also give it other "handlers" too. For example, we can also give it the underlined handler:
 
-Version 1 type numerals are not a good choice for the simply-typed
-lambda calculus.  The reason is that each different numberal has a
-different type!  For instance, if zero has type &sigma;, then `false`
-has type &tau; --> &tau; --> &tau;, for some &tau;.  Since one is
-represented by the function `\x.x false 0`, one must have type (&tau;
---> &tau; --> &tau;) --> &sigma; --> &sigma;.  But this is a different
-type than zero!  Because each number has a different type, it becomes
-impossible to write arithmetic operations that can combine zero with
-one.  We would need as many different addition operations as we had
-pairs of numbers that we wanted to add.
 
-Fortunately, the Church numberals are well behaved with respect to
-types.  They can all be given the type (&sigma; --> &sigma;) -->
-&sigma; --> &sigma;.
+       the_search (\search_result. larger_computation search_result other_arguments)
+                          ------------------------------------------------------------------
 
+This "handler" encodes the search's having finished, and delivering a final
+answer to whatever else you wanted your program to do with the result of the
+search. If you like, at any stage in the search you might just give an argument
+to *this* handler, instead of giving an argument to the handler that continues
+the list traversal leftwards. Semantically, this would amount to *aborting* the
+list traversal! (As we've said before, whether the rest of the list traversal
+really gets evaluated will depend on what evaluation order is in place. But
+semantically we'll have avoided it. Our larger computation  won't depend on the
+rest of the list traversal having been computed.)
+
+Do you have the basic idea? Think about how you'd implement it. A good
+understanding of the v2 lists will give you a helpful model.
+
+In broad outline, a single stage of the search would look like before, except
+now f would receive two extra, "handler" arguments.
+
+       f 3 <result of folding f and z over [2; 1]> <handler to continue folding leftwards> <handler to abort the traversal>
+
+`f`'s job would be to check whether `3` matches the element we're searching for
+(here also `3`), and if it does, just evaluate to the result of passing `true` to
+the abort handler. If it doesn't, then evaluate to the result of passing
+`false` to the continue-leftwards handler.
+
+In this case, `f` wouldn't need to consult the result of folding `f` and `z` over `[2;
+1]`, since if we had found the element `3` in more rightward positions of the
+list, we'd have called the abort handler and this application of `f` to `3` etc
+would never be needed. However, in other applications the result of folding `f`
+and `z` over the more rightward parts of the list would be needed. Consider if
+you were trying to multiply all the elements of the list, and were going to
+abort (with the result `0`) if you came across any element in the list that was
+zero. If you didn't abort, you'd need to know what the more rightward elements
+of the list multiplied to, because that would affect the answer you passed
+along to the continue-leftwards handler.
+
+A **version 5** list encodes the kind of fold operation we're envisaging here, in
+the same way that v3 (and [v4](/advanced/#index1h1)) lists encoded the simpler fold operation.
+Roughly, the list `[5;4;3;2;1]` would look like this:
+
+
+       \f z continue_leftwards_handler abort_handler.
+               <fold f and z over [4;3;2;1]>
+               (\result_of_fold_over_4321. f 5 result_of_fold_over_4321  continue_leftwards_handler abort_handler)
+               abort_handler
+
+       ; or, expanding the fold over [4;3;2;1]:
+
+       \f z continue_leftwards_handler abort_handler.
+               (\continue_leftwards_handler abort_handler.
+                       <fold f and z over [3;2;1]>
+                       (\result_of_fold_over_321. f 4 result_of_fold_over_321 continue_leftwards_handler abort_handler)
+                       abort_handler
+               )
+               (\result_of_fold_over_4321. f 5 result_of_fold_over_4321  continue_leftwards_handler abort_handler)
+               abort_handler
+
+       ; and so on             
+       
+Remarks: the `larger_computation` handler should be supplied as both the
+`continue_leftwards_handler` and the `abort_handler` for the leftmost
+application, where the head `5` is supplied to `f`; because the result of this
+application should be passed to the larger computation, whether it's a "fall
+off the left end of the list" result or it's a "I'm finished, possibly early"
+result. The `larger_computation` handler also then gets passed to the next
+rightmost stage, where the head `4` is supplied to `f`, as the `abort_handler` to
+use if that stage decides it has an early answer.
+
+Finally, notice that we don't have the result of applying `f` to `4` etc given as
+an argument to the application of `f` to `5` etc. Instead, we pass
+
+       (\result_of_fold_over_4321. f 5 result_of_fold_over_4321 <one_handler> <another_handler>)
+
+*to* the application of `f` to `4` as its "continue" handler. The application of `f`
+to `4` can decide whether this handler, or the other, "abort" handler, should be
+given an argument and constitute its result.
+
+
+I'll say once again: we're using temporally-loaded vocabulary throughout this,
+but really all we're in a position to mean by that are claims about the result
+of the complex expression semantically depending only on this, not on that. A
+demon evaluator who custom-picked the evaluation order to make things maximally
+bad for you could ensure that all the semantically unnecessary computations got
+evaluated anyway. We don't have any way to prevent that. Later,
+we'll see ways to *semantically guarantee* one evaluation order rather than
+another. Though even then the demonic evaluation-order-chooser could make it
+take unnecessarily long to compute the semantically guaranteed result. Of
+course, in any real computing environment you'll know you're dealing with a
+fixed evaluation order and you'll be able to program efficiently around that.
+
+In detail, then, here's what our v5 lists will look like:
+
+       let empty = \f z continue_handler abort_handler. continue_handler z  in
+       let make_list = \h t. \f z continue_handler abort_handler.
+               t f z (\sofar. f h sofar continue_handler abort_handler) abort_handler  in
+       let isempty = \lst larger_computation. lst
+                       ; here's our f
+                       (\hd sofar continue_handler abort_handler. abort_handler false)
+                       ; here's our z
+                       true
+                       ; here's the continue_handler for the leftmost application of f
+                       larger_computation
+                       ; here's the abort_handler
+                       larger_computation  in
+       let extract_head = \lst larger_computation. lst
+                       ; here's our f
+                       (\hd sofar continue_handler abort_handler. continue_handler hd)
+                       ; here's our z
+                       junk
+                       ; here's the continue_handler for the leftmost application of f
+                       larger_computation
+                       ; here's the abort_handler
+                       larger_computation  in
+       let extract_tail = ; left as exercise
+
+These functions are used like this:
+
+       let my_list = make_list a (make_list b (make_list c empty) in
+       extract_head my_list larger_computation
+
+If you just want to see `my_list`'s head, the use `I` as the
+`larger_computation`.
+
+What we've done here does take some work to follow. But it should be within
+your reach. And once you have followed it, you'll be well on your way to
+appreciating the full terrible power of continuations.
+
+<!-- (Silly [cultural reference](http://www.newgrounds.com/portal/view/33440).) -->
+
+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.
diff --git a/week5.mdwn b/week5.mdwn
new file mode 100644 (file)
index 0000000..a13c55e
--- /dev/null
@@ -0,0 +1,158 @@
+[[!toc]]
+
+##The simply-typed lambda calculus##
+
+The uptyped lambda calculus is pure computation.  It is much more
+common, however, for practical programming languages to be typed.
+Likewise, systems used to investigate philosophical or linguistic
+issues are almost always typed.  Types will help us reason about our
+computations.  They will also facilitate a connection between logic
+and computation.
+
+Soon we will consider polymorphic type systems.  First, however, we
+will consider the simply-typed lambda calculus.  There's good news and
+bad news: the good news is that the simply-type lambda calculus is
+strongly normalizing: every term has a normal form.  We shall see that
+self-application is outlawed, so &Omega; can't even be written, let
+alone undergo reduction.  The bad news is that fixed-point combinators
+are also forbidden, so recursion is neither simple nor direct.
+
+#Types#
+
+We will have at least one ground type, `o`.  From a linguistic point
+of view, think of the ground types as the bar-level 0 categories, that
+is, the lexical types, such as Noun, Verb, Preposition (glossing over
+the internal complexity of those categories in modern theories).
+
+In addition, there will be a recursively-defined class of complex
+types `T`, the smallest set such that
+
+*    ground types, including `o`, are in `T`
+
+*    for any types &sigma; and &tau; in `T`, the type &sigma; -->
+     &tau; is in `T`.
+
+For instance, here are some types in `T`:
+
+     o
+     o --> o
+     o --> o --> o
+     (o --> o) --> o
+     (o --> o) --> o --> o
+
+and so on.
+
+#Typed lambda terms#
+
+Given a set of types `T`, we define the set of typed lambda terms <code>&Lambda;_T</code>,
+which is the smallest set such that
+
+*    each type `t` has an infinite set of distinct variables, {x^t}_1,
+     {x^t}_2, {x^t}_3, ...
+
+*    If a term `M` has type &sigma; --> &tau;, and a term `N` has type
+     &sigma;, then the application `(M N)` has type &tau;.
+
+*    If a variable `a` has type &sigma;, and term `M` has type &tau;, 
+     then the abstract <code>&lambda; a M</code> has type &sigma; --> &tau;.
+
+The definitions of types and of typed terms should be highly familiar
+to semanticists, except that instead of writing &sigma; --> &tau;,
+linguists (following Montague, who followed Church) write <&sigma;,
+&tau;>.  We will use the arrow notation, since it is more iconic.
+
+Some examples (assume that `x` has type `o`):
+
+      x            o
+      \x.x         o --> o
+      ((\x.x) x)   o
+
+Excercise: write down terms that have the following types:
+
+                   o --> o --> o
+                   (o --> o) --> o --> o
+                   (o --> o --> o) --> o
+
+#Associativity of types versus terms#
+
+As we have seen many times, in the lambda calculus, function
+application is left associative, so that `f x y z == (((f x) y) z)`.
+Types, *THEREFORE*, are right associative: if `f`, `x`, `y`, and `z`
+have types `a`, `b`, `c`, and `d`, respectively, then `f` has type `a
+--> b --> c --> d == (a --> (b --> (c --> d)))`.
+
+It is a serious faux pas to associate to the left for types.  You may
+as well use your salad fork to stir your tea.
+
+#The simply-typed lambda calculus is strongly normalizing#
+
+If `M` is a term with type &tau; in &Lambda;_T, then `M` has a
+normal form.  The proof is not particularly complex, but we will not
+present it here; see Berendregt or Hankin.
+
+Since &Omega; does not have a normal form, it follows that &Omega;
+cannot have a type in &Lambda;_T.  We can easily see why:
+
+     &Omega; = (\x.xx)(\x.xx)
+
+Assume &Omega; has type &tau;, and `\x.xx` has type &sigma;.  Then
+because `\x.xx` takes an argument of type &sigma; and returns
+something of type &tau;, `\x.xx` must also have type &sigma; -->
+&tau;.  By repeating this reasoning, `\x.xx` must also have type
+(&sigma; --> &tau;) --> &tau;; and so on.  Since variables have
+finite types, there is no way to choose a type for the variable `x`
+that can satisfy all of the requirements imposed on it.
+
+In general, there is no way for a function to have a type that can
+take itself for an argument.  It follows that there is no way to
+define the identity function in such a way that it can take itself as
+an argument.  Instead, there must be many different identity
+functions, one for each type.
+
+#Typing numerals#
+
+Version 1 type numerals are not a good choice for the simply-typed
+lambda calculus.  The reason is that each different numberal has a
+different type!  For instance, if zero has type &sigma;, then `false`
+has type &tau; --> &tau; --> &tau;, for some &tau;.  Since one is
+represented by the function `\x.x false 0`, one must have type (&tau;
+--> &tau; --> &tau;) --> &sigma; --> &sigma;.  But this is a different
+type than zero!  Because each number has a different type, it becomes
+impossible to write arithmetic operations that can combine zero with
+one.  We would need as many different addition operations as we had
+pairs of numbers that we wanted to add.
+
+Fortunately, the Church numberals are well behaved with respect to
+types.  They can all be given the type (&sigma; --> &sigma;) -->
+&sigma; --> &sigma;.
+
+
+
+
+
+<!--
+
+Mau integrate some mention of this at some point.
+
+http://okmij.org/ftp/Computation/lambda-calc.html#predecessor
+
+
+Predecessor and lists are not representable in simply typed lambda-calculus
+
+    The predecessor of a Church-encoded numeral, or, generally, the encoding of a list with the car and cdr operations are both impossible in the simply typed lambda-calculus. Henk Barendregt's ``The impact of the lambda-calculus in logic and computer science'' (The Bulletin of Symbolic Logic, v3, N2, June 1997) has the following phrase, on p. 186:
+
+        Even for a function as simple as the predecessor lambda definability remained an open problem for a while. From our present knowledge it is tempting to explain this as follows. Although the lambda calculus was conceived as an untyped theory, typeable terms are more intuitive. Now the functions addition and multiplication are defineable by typeable terms, while [101] and [108] have characterized the lambda-defineable functions in the (simply) typed lambda calculus and the predecessor is not among them [the story of the removal of Kleene's four wisdom teeth is skipped...]
+        Ref 108 is R.Statman: The typed lambda calculus is not elementary recursive. Theoretical Comp. Sci., vol 9 (1979), pp. 73-81.
+
+    Since list is a generalization of numeral -- with cons being a successor, append being the addition, tail (aka cdr) being the predecessor -- it follows then the list cannot be encoded in the simply typed lambda-calculus.
+
+    To encode both operations, we need either inductive (generally, recursive) types, or System F with its polymorphism. The first approach is the most common. Indeed, the familiar definition of a list
+
+         data List a = Nil | Cons a (List a)
+
+    gives an (iso-) recursive data type (in Haskell. In ML, it is an inductive data type).
+
+    Lists can also be represented in System F. As a matter of fact, we do not need the full System F (where the type reconstruction is not decidable). We merely need the extension of the Hindley-Milner system with higher-ranked types, which requires a modicum of type annotations and yet is able to infer the types of all other terms. This extension is supported in Haskell and OCaml. With such an extension, we can represent a list by its fold, as shown in the code below. It is less known that this representation is faithful: we can implement all list operations, including tail, drop, and even zip.
+
+
+-->