;; The first three lines of this file were inserted by DrScheme. They record metadata ;; about the language level of this file in a form that our tools can easily process. #reader(lib "reader.ss" "plai" "lang") ; CS334: Principles of Programming languages ; Prof. McGuire ; ; Here's a derivation of the Y combinator in Scheme (or for other ; eager languages), using factorial as a running example. See ; PLAI 22 for further discussion. ; ; [This is a unicode source file! If you're reading this in a web ; browser instead of DrScheme, beware that you'll see λ instead ; of the lambda character.] ; Here's what we're trying to build: (define factorial (λ (n) (if (zero? n) 1 (* n (factorial (sub1 n)))))) ; Generates 4! = 24 {factorial 4} ; That implementation of factorial depends on DEFINE (or LETREC) to ; create the recursion by allowing the body of the LAMBDA to refer ; to the variable it will eventually be bound to. This is because ; inside of the body of the λ, factorial is a free variable. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; We could instead pass the factorial function that we need to ; itself as an argument. Let procedure r be this modified version: ; (define r (λ (r n) (if (zero? n) 1 (* n (r r (sub1 n)))))) ; ; (factorial n) => (r r n) ; or... (define (factorial n) (r r n)) (r r 4) ; Now r is not free on the body of the λ. The problem with this ; version is that we're still using DEFINE to bind r. But since ; r is not free on its body, we don't actually need DEFINE and ; can rewrite this with λ + APP. ; ; In the example below, the 1st λ just runs a procedure r on itself. ; The 2nd λ creates the factorial procedure itself. Note that ; when we reach the actual recursive call we now have the ; self-application (r r) in order to keep passing p down the call ; chain. We call the procedure r because it needs to call itself ; recursively to work. Note that the code also Currys (r r (sub1 n)) ; into ((r r) (sub1 n)); the reason for this will be clear later. ; Generates 4! = 24 without using DEFINE or LETREC { ; Apply the following function to itself ((λ (r) (r r)) ; Here's the function; it *is* r, and r is its argument as well! (λ (r) (λ (n) (if (zero? n) 1 (* n ((r r) (sub1 n))))))) 4} ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; Observe that ( ) = ((λ (k) ( k)) ). ; Therefore: ; ((r r) (sub1 n)) = ((λ (k) ((r r) k)) (sub1 n)) ; ; Substituting this into our previous expression produces: ; { ((λ (r) (r r)) ; Definition of "r", which is its own argument: (λ (r) (λ (n) (if (zero? n) 1 (* n ((λ (k) ((r r) k)) (sub1 n))))))) 4} ; Note that the "recursive call" now uses a single function ; (λ (k) ((r r) k)). Call that function "f"; it *is* factorial, ; provided we pass along the right value of r. Since we know that ; we can rewrite a LET as LAMBDA + APP, I'll temporarily use a LET to ; name this function and make this more clear: { ((λ (r) (r r)) ; Definition of "r", which is its own argument: (λ (r) (let ([f (λ (k) ((r r) k))]) ; Here's factorial, named "f": (λ (n) (if (zero? n) 1 (* n (f (sub1 n)))))))) 4} ; Rewritten without the LET: { ; Self-application to propagate r down the call-chain: ((λ (r) (r r)) ; Definition of "r", which is its own argument: (λ (r) ; Expanded LET: ((λ (f) ; Here's factorial, named "f": (λ (n) (if (zero? n) 1 (* n (f (sub1 n)))))) ; Here's the definition of f: (λ (k) ((r r) k))) 4} ; Note that we've isolated the factorial-specific code above. ; Call everything inside the (λ (f) ...) the "generator function" ; g and extract it from the rest: { ; (let ([g ...]) expanded: ((λ (g) ; Self-application to propagate r down the call-chain: ((λ (r) (r r)) ; Definition of "r", which is its own argument: (λ (r) (g ; Here's the definition of f: (λ (k) ((r r) k)))))) ; The generator, g: (λ (f) ; Here's factorial, named "f": (λ (n) (if (zero? n) 1 (* n (f (sub1 n))))))} 4} ; The top piece, (λ (g) ...) has nothing to do with factorial. ; It is a generalized fixed point combinator for creating ; single-argument recursive functions. We call this "Y". ; ; The bottom piece, which defines g, has nothing to do with the ; fixed point combinator.It is just the generator function for ; factorial. Let's separate them: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; Y combinator ; ; Let g = Generator function (what we want the fixed point of) ; This is a function that, given f, returns another ; function that explicitly performs one iteration of f ; and then calls f for the remainder of the iterations. ; ; Let k = Argument to the recursive function that f we are trying ; to generate. ; ; Let r = The base recursive function. It invokes the generator ; on a function. We create two copies of this, each of which ; calls the other. (define Y (λ (g) ((λ (r) (r r)) (λ (r) (g (λ (k) ((r r) k))))))) ; Here's the Y combinator without the initial self application to ; show where the two copies of r come from: (define Y-expanded (λ (g) ((λ (r2) (g (λ (k) ((r2 r2) k)))) (λ (r1) (g (λ (n) ((r1 r1) k))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Generator function for factorial (define fact-gen (λ (f) (λ (n) (if (zero? n) 1 (* n (f (sub1 n))))))) {(Y fact-gen) 4} ; To demonstrate that we really don't need DEFINE: {{ (λ (g) ((λ (z) (z z)) (λ (x) (g (λ (y) ((x x) y)))))) (λ (f) (λ (n) (if (zero? n) 1 (* n (f (sub1 n))))))} 4} ; Now we can rewrite any definition of the form: ; ; (letrec ([fcn (λ (n) body)]) ; (fcn exp)) ; => ; ((Y (λ (f) (λ (n) body))) exp) ; ; Thus eliminating LETREC from Scheme! ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; Use the Y-combinator to create a different recursive procedure, ; demonstrating its generality: (define sum-gen (λ (f) (λ (L) (if (empty? L) 0 (+ (first L) (f (rest L))))))) ; Generates 10+5+2+1+0 = 18 ((Y sum-gen) '(10 5 2 1))