#lang racket ; The applicative-order Y combinator ; ; Or how to define a recursive function without giving it a name ; before the definition is complete. ; ; One notion of "definition" is that of an abbreviation. I.e. a ; a shorthand name for a piece of text. When used, the name is simply ; expanded into the text it stands for. Many languages call these ; macros. In general, an abbreviation is not supposed to be ; circular, so that a sequence of substitutions (aka macro expansions) ; always terminates in a constant number of steps. ; ; If an abbreviation is a term that does not create side effects, then ; abbreviations can be replaced by their values. For example, a lambda ; term with no free variables can be abbreviated: (define g (lambda (x) (+ x 1))) (g (g 2)) ; is the same as ( (lambda (x) (+ x 1)) ((lambda (x) (+ x 1)) 2)) ; But a recursive definition is different. For example ; here is a typical recursive definition of f in terms of itself. (define f (lambda (x) (if (> 0 x) x (f (- x 2))) ) ) ; Simple text substitution is not going to terminate, and furthermore ; we are using f before it is actually defined. That is, f is a free ; term inside the text that is being used to define f. ; Of course, Scheme is perfectly happy with this: (f 2) ; and we are now going to explain why. ; Let's change our defintion a bit, so that the identifier f is bound ; inside the defining term: (lambda (f) (lambda (x) (if (> 0 x) x (f (- x 2))) ) ) ; so now there are no free variable in the defining term. ; But this thing we just defined is not f, it is the ; function that builds f when given f! ; (define f-builder ; (lambda (f) ; (lambda (x) ; (if (> 0 x) x (f (- x 2))) ; ) ; ) ; ) ; The function f is this: ; (f-builder f). ; But we don't have f! We need to build it. ; So f is this: ; (f-builder (f-builder f)) ; That is (f-builder (f-builder (f-builder (..... f)))) ; Arrg!!! It doesn't look like we have accomplished anything. ; But in fact we have. Because now we can defer some of the definition of ; f to run time (evaluation time). Getting something like this ; (define f ; (lambda (x) ; (if (> 0 x) x ((f-builder ?) (- x 2))) ; ) ; ) ; This new defintion has no free variables (after substituing f-builder) ; except is does have the unknown term ?. But we only need the value of ? ; when we are in the recursion. In the base case we never evaluate ; f-builder. ; In other words, in the construction of f we have two cases driven by the ; input argument: ; The base case can compute the function result directly, without needing ; f and thus does not need to construct f. ; The recursion needs to construct f prior to recursing, and ; the recursion might need the builder. ; So the builder of f needs to be able to call itself. ; Since we don't have a defined name for it (that would be cheating), ; we need to pass it it's own name! ; So, now we define a new variant of f-builder, that instead of ; taking f as an argument, takes itself, so that it can build f. That ; is (f-builder f-builder) constructs f, ; so f-buiilder will look like this: (lambda (f-builder) (lambda (x) (if (> 0 x) x ( (f-builder f-builder) (- x 2)) ) ) ) ; So now we just need to start this process by having the above ; expression call itself. ; But wait, how can you call yourself when you don't have a name? ; This is easily done with this kind of function (define call-yourself-with-yourself (lambda (d) (d d) )) ; so we get something like this ( (lambda (d) (d d)) ; i.e. call yourself with yourself ; the f-builder that expects itself: (lambda (f-builder) (lambda (x) (if (> 0 x) x ( (f-builder f-builder) (- x 2)) ) ) ) ) ; ok, does this do what we want? (define f-version-1 ( (lambda (d) (d d)) (lambda (f-builder) (lambda (x) (if (> 0 x) x ( (f-builder f-builder) (- x 2)) ) ) ) )) (f-version-1 -1) (f-version-1 0) (f-version-1 2) ; Wow, that actually works without going off the deep end of recursion! ; Why? Note how only in the recursion is f-builder invoked. ; So, now we have a function f-version-1 that is f, but it would be nice if ; our definition of f actually look like a typical recursive one, rather ; than the above which constructs f when needed. ; Let us introduce f-def as the term defining f. Then we can write things ; this way (which is a simple defintion, not recursive): (define f-def (lambda (f) (lambda (x) (if (> 0 x) x ( f (- x 2)) ) ) ) ) ; So we need to pass in the defintion of f, that is, ; (f-builder f-builder) ; or (call-yourself-with-yourself f-builder), ; into f-def so that it can build f ; (define f-version-2 ; ( ; apply the general builder ; (lambda (function-def) ; ; ( (lambda (d) (d d)) ; i.e. call yourself ; ; (lambda (function-builder) ; (begin (print "Invoking function-builder") (newline) ) ; ; ; this recursion does not terminate when evaluating: ; ; (function-builder function-builder) ; (function-def (function-builder function-builder) ) ; ) ; ) ; ) ; ; ; to the defintion of f ; f-def ; ) ; ) ; ; (f-version-2 0) ; (define f-version-3 ( ; apply the builder (lambda (function-def) ( (lambda (d) (d d)) ; i.e. call yourself (lambda (function-builder) ; (begin (print "Invoking function-builder") (newline) ) ; delay the evaluation of (function-builder function-builder) until ; it is needed. That is, make it a function, and this is f! (function-def (lambda (y) ( (function-builder function-builder) y) ) ) ) ) ) ; to the definition f-def ) ) (map f-version-3 '(0 1 2 3)) ; The general builder is completely independent of the definition of f, so we ; can create the function (combinator) the converts a definition into the ; actual function (define Y-comb (lambda (function-def) ( (lambda (d) (d d)) ; i.e. call yourself (lambda (function-builder) ; delay the evaluation of (function-builder function-builder) until ; it is needed. That is, make it a function, and this is f! (function-def (lambda (y) ((function-builder function-builder) y) ) ) ) ) ) ) (map (Y-comb f-def) '(0 1 2 3)) ; Amazing, we can now allow definitions in our restricted subset of Scheme! ( (Y-comb f-def) 3 ) ; do not try this at home: (Y-comb Y-comb)