2. Why do we need the Y combinator?
For fundamental understanding of
recursion, we hope to create
recursive functions using the
lambda calculus:
x | t t | λx.t
3. Why not define or letrec?
(define length (letrec ([length
(lambda (ls) (lambda (ls)
(cond (cond
[(null? ls) 0] [(null? ls) 0]
[else (add1 (length (cdr ls)))]))) [else (add1 (length (cdr ls)))]))])
(length '(a b c)))
1. We don’t have define or letrec in lambda calculus
2. For fundamental understanding of recursion, we
want to see how define and letrec can be created
using just the three elements of lambda calculus:
x | t t | λx.t
3. You will see how this understanding can be useful
when constructing compilers
4. Plan
(define length
(lambda (ls)
(cond
[(null? ls) 0]
[else (add1 (length (cdr ls)))])))
• We start by constructing a recursive definition of
“length” in a pure subset of Scheme
• Then extract a common pattern that can be
applied to recursive definitions in general
• This common pattern is the Y combinator
5. How do we do that?
• First, notice we can’t really define a recursive
function without binding it to a name
• Second, answer this question:
“Where can we bind something to a name?”
• The answer is: λx.t
• Lambda, the ultimate binder
6. Step 1: Binder
(define length
(lambda (ls)
(cond
[(null? ls) 0]
[else (add1 (length (cdr ls)))])))
• Step1: create a lambda similar to this define
• This creates a binder where we can bind the function to
• Our goal: bind the function “length” to the name length
7. Step 1: Binder
(lambda (length)
(lambda (ls)
(cond
[(null? ls) 0]
[else (add1 (length (cdr ls)))])))
• Step1: create a lambda similar to this define
• This creates a binder where we can bind the function to
• Our goal: bind the function “length” to the name length
8. Step 2: Copy
((lambda (length)
(lambda (ls)
(cond
[(null? ls) 0]
[else (add1 (length (cdr ls)))])))
(lambda (length)
(lambda (ls)
(cond
[(null? ls) 0]
[else (add1 (length (cdr ls)))]))))
• Make a copy of the function and apply itself
to the copy (self-application)
• This will successfully bind the name length
to the function “itself”
9. Step 3: Small fix
((lambda (length)
(lambda (ls)
(cond
[(null? ls) 0]
[else (add1 ((length length) (cdr ls)))])))
(lambda (length)
(lambda (ls)
(cond
[(null? ls) 0]
[else (add1 ((length length) (cdr ls)))]))))
The first argument to the application of
“length” should be itself
10. Step 4: Extract Patterns
((lambda (length)
(lambda (ls)
(cond
[(null? ls) 0]
[else (add1 ((length length) (cdr ls)))])))
(lambda (length)
(lambda (ls)
(cond
[(null? ls) 0]
[else (add1 ((length length) (cdr ls)))]))))
• This recursive function will work (try it!)
• This is called “poor man’s Y”
• Now we are going to extract the pattern
in there, so that the same pattern works
for any function.
11. Step 4: Extract Patterns
((lambda (length)
(lambda (ls)
(cond • But we can’t see the original
[(null? ls) 0] definition in there.
[else (add1 ((length length) (cdr ls)))]))) • We hope to see this, but the
(lambda (length) self-applications (length length)
(lambda (ls) bother us.
(cond • Hope we can get rid of them
[(null? ls) 0]
while preserving the semantics.
[else (add1 ((length length) (cdr ls)))]))))
• This recursive function will work (try it!)
• This is called “poor man’s Y” (lambda (length)
• Now we are going to extract the pattern (lambda (ls)
(cond
in there, so that the same pattern works [(null? ls) 0]
for any function. [else (add1 (length (cdr ls)))])))
12. Three Self-applications
((lambda (length)
(lambda (ls)
(cond
[(null? ls) 0]
[else (add1 ((length length) (cdr ls)))])))
(lambda (length)
(lambda (ls)
(cond
[(null? ls) 0]
[else (add1 ((length length) (cdr ls)))]))))
Notice that this code has
three self-aplications, one
outer and two inner.
13. Abstract Outer Self-application
((lambda (length)
(lambda (ls)
(cond ((lambda (u) (u u))
[(null? ls) 0] (lambda (length)
[else (add1 ((length length) (cdr ls)))]))) (lambda (ls)
(lambda (length) (cond
(lambda (ls) [(null? ls) 0]
(cond [else (add1 ((length length) (cdr ls)))]))))
[(null? ls) 0]
[else (add1 ((length length) (cdr ls)))]))))
• First, let’s extract the pattern
which does the outer self-
application
• In compiler terms, this is called
“common subexpression
elimination”
14. Inner Self-application
((lambda (length)
(lambda (ls)
(cond ((lambda (u) (u u))
[(null? ls) 0] (lambda (length)
[else (add1 ((length length) (cdr ls)))]))) (lambda (ls)
(lambda (length) (cond
(lambda (ls) [(null? ls) 0]
(cond [else (add1 ((length length) (cdr ls)))]))))
[(null? ls) 0]
[else (add1 ((length length) (cdr ls)))]))))
Now we have only one self-
application left (why not two?)
15. Abstract Inner Self-application
((lambda (u) (u u))
((lambda (u) (u u)) (lambda (length)
(lambda (length) ((lambda (g)
(lambda (ls) (lambda (ls)
(cond (cond
[(null? ls) 0] [(null? ls) 0]
[else (add1 ((length length) (cdr ls)))])))) [else (add1 (g (cdr ls)))])))
(length length))))
We can now extract the inner
self-application • Done in a very similar way
as the outer one.
• We may call it “factor out”
16. Function is there!
((lambda (u) (u u))
((lambda (u) (u u)) (lambda (length)
(lambda (length) ((lambda (g)
(lambda (ls) (lambda (ls)
(cond (cond
[(null? ls) 0] [(null? ls) 0]
[else (add1 ((length length) (cdr ls)))])))) [else (add1 (g (cdr ls)))])))
(length length))))
• Notice that this part is exactly (define length
(lambda (ls)
the definition of “length” (cond
(modulo alpha-equivalence) [(null? ls) 0]
[else (add1 (length (cdr ls)))])))
• We are almost done!
17. Non-termination (CBV)
used to be here
((lambda (u) (u u))
(lambda (length)
((lambda (g)
(lambda (ls)
(cond
[(null? ls) 0]
[else (add1 (g (cdr ls)))])))
(length length))))
• But notice that (length length)
went outside of (lambda (ls) …)
• This will cause non-termination if
the language is call-by-value
(why?)
18. Eta-expansion
((lambda (u) (u u)) ((lambda (u) (u u))
(lambda (length) (lambda (length)
((lambda (g) ((lambda (g)
(lambda (ls) (lambda (ls)
(cond (cond
[(null? ls) 0] [(null? ls) 0]
[else (add1 (g (cdr ls)))]))) [else (add1 (g (cdr ls)))])))
(length length)))) (lambda (v) ((length length) v))))
• Eta-expand (length length) will
prevent the non-termination while
preserving the semantics
19. Abstract out the function
((lambda (f)
((lambda (u) (u u))
((lambda (u) (u u))
(lambda (length)
(lambda (length)
(f
((lambda (g)
(lambda (v) ((length length) v))))))
(lambda (ls)
(cond “length”
(lambda (g)
[(null? ls) 0]
(lambda (ls)
[else (add1 (g (cdr ls)))])))
(cond
(lambda (v) ((length length) v))))
[(null? ls) 0]
[else (add1 (g (cdr ls)))]))))
• Now we can factor out the
function “length”
• Notice that we can now
substitute f for any function
and get a recursive definition!
20. This is Y combinator!
Y combinator!
Y combinator
((lambda (f)
((lambda (u) (u u))
((lambda (u) (u u))
(lambda (length)
(lambda (length)
(f
((lambda (g)
(lambda (v) ((length length) v))))))
(lambda (ls)
(cond “length”
(lambda (g)
[(null? ls) 0]
(lambda (ls)
[else (add1 (g (cdr ls)))])))
(cond
(lambda (v) ((length length) v))))
[(null? ls) 0]
[else (add1 (g (cdr ls)))]))))
• Now we can factor out the
function “length”
• Notice that we can now
substitute f for any function
and get a recursive definition!
21. Renaming
(lambda (f) (lambda (f)
((lambda (u) (u u)) ((lambda (u) (u u))
(lambda (length) (lambda (x)
(f (f
(lambda (v) ((length length) v))))) (lambda (v) ((x x) v))))))
Does the name “length” matter • Obviously no!
here? • So we can rename it
22. Expanding
(lambda (f) (lambda (f)
((lambda (u) (u u)) ((lambda (x) (f (lambda (v) ((x x) v))))
(lambda (x) (f (lambda (v) ((x x) v)))))) (lambda (x) (f (lambda (v) ((x x) v))))))
Or, if you would like self-
application expanded out,
this is just another form
23. CBV and CBN
Y combinator (call-by-value)
(lambda (f)
((lambda (x) (f (lambda (v) ((x x) v))))
(lambda (x) (f (lambda (v) ((x x) v))))))
Y combinator (call-by-name)
Or, if the language is call-by- (lambda (f)
name, we get this instead ((lambda (x) (f (x x))))
(without eta-expansion) (lambda (x) (f (x x))))))