[[!toc]] #Q: How do you know that every term in the untyped lambda calculus has a fixed point?# 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).
``````let L = \x. T (x x) in
let X = L L in
X ≡ L L ≡ (\x. T (x x)) L ~~> T (L L) ≡ T X
``````
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`, `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 ≡ L L ≡ (\x. T (x x)) (\x. T (x x))`. 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 (x x)) (\x. T (x x)) in
Y Y
≡   \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))...)))
``````
#Q: Ouch! Stop hurting my brain.# 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)...) 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 (x x)) (\x. succ (x x)) in
succ X
≡   succ ( (\x. succ (x x)) (\x. succ (x x)) )
~~> succ (succ ( (\x. succ (x x)) (\x. succ (x x)) ))
≡   succ (succ X)
``````
You should see the close similarity with `Y Y` 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
≡    (\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))
``````
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 `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.) This is amazing, by the way: we're proving things about a term that represents arithmetic infinity. It's important to bear in mind the simplest term in question is not infinite: 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 the factorial function, evaluation order is crucial. In the following computation, we will arrive at a normal form. Watch for the moment at 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 prefact = \f n. iszero n 1 (mul n (f (pred n))) in
let fact = Y prefact in
fact 2
≡   [(\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
≡   [ (\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))
≡   mul 2 (mul 1 (iszero 0 1 (mul 1 ([prefact ((\x. prefact (x x)) (\x. prefact (x x)))] (pred 0)))))
~~> mul 2 (mul 1 1)
~~> mul 2 1
~~> 2
``````
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`, no matter what the ... contains; 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 `prefact` if we are forced to. #Q. You claimed that the Ackermann 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 Ackermann function using full recursion.# A. OK: 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?# * What is it about the variant fixed-point combinators that makes them compatible with a call-by-value evaluation strategy? * How do you know that the Ackermann function can't be computed using primitive recursion techniques? * What *exactly* is primitive recursion? * I hear that `Y` delivers the *least* fixed point. Least according to what ordering? How do you know it's least? Is leastness important? #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. > *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 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](/implementing_trees). #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 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: .................. ~~> .................. false ~~> ............. ~~> ............. false ~~> ......... ~~> ......... 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. 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 *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. 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. 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: pair (\x y. add x y) or: pair (\x y. x) to get the first element of the pair. Of course you can lift that if you want:
``extract_fst ≡ \pair. pair (\x y. x)``