Reinventing the Y combinator

37,128 views

Published on

Published in: Technology
1 Comment
31 Likes
Statistics
Notes
No Downloads
Views
Total views
37,128
On SlideShare
0
From Embeds
0
Number of Embeds
28,983
Actions
Shares
0
Downloads
0
Comments
1
Likes
31
Embeds 0
No embeds

No notes for slide

Reinventing the Y combinator

  1. 1. How to Reinventthe Y combinator Yin Wang
  2. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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))))))
  24. 24. Test (length)(((lambda (f) ((lambda (x) (f (lambda (v) ((x x) v)))) (lambda (x) (f (lambda (v) ((x x) v)))))) (lambda (length) (lambda (ls) (cond [(null? ls) 0] [else (add1 (length (cdr ls)))])))) (a b c))==> 3
  25. 25. Test (factorial)(((lambda (f) ((lambda (x) (f (lambda (v) ((x x) v)))) (lambda (x) (f (lambda (v) ((x x) v)))))) (lambda (fact) (lambda (n) (cond [(zero? n) 1] [else (* n (fact (sub1 n)))])))) 5)==> 120

×