;; 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))