I've been looking into how languages that forbid use-before-def and don't have mutable cells (no set!
or setq
) can nonetheless provide recursion. I of course ran across the (famous? infamous?) Y combinator and friends, e.g.:
- http://www.ece.uc.edu/~franco/C511/html/Scheme/ycomb.html
- http://okmij.org/ftp/Computation/fixed-point-combinators.html
- http://www.angelfire.com/tx4/cus/combinator/birds.html
- http://en.wikipedia.org/wiki/Fixed-point_combinator
When I went to implement "letrec" semantics in this style (that is, allow a local variable to be defined such that it can be a recursive function, where under the covers it doesn't ever refer to its own name), the combinator I ended up writing looks like this:
Y_letrec = λf . (λx.x x) (λs . (λa . (f ((λx.x x) s)) a))
Or, factoring out the U combinator:
U = λx.x x
Y_letrec = λf . U (λs . (λa . (f (U s)) a))
Read this as: Y_letrec is a function which takes a to-be-recursed function f
.
f
must be a single-argument function which accepts s
, where s
is the function
that f
can call to achieve self-recursion. f
is expected to define and return
an "inner" function which does the "real" operation. That inner function accepts
argument a
(or in the general case an argument list, but that can't be expressed
in the traditional notation). The result of calling Y_letrec is a result of calling
f
, and it is presumed to be an "inner" function, ready to be called.
The reason I set things up this way is so that I could use the parse tree form of the to-be-recursed function directly, without modification, merely wrapping an additional function layer around it during transformation when handling letrec. E.g., if the original code is:
(letrec ((foo (lambda (a) (foo (cdr a))))))
then the transformed form would be along the lines of:
(define foo (Y_letrec (lambda (foo) (lambda (a) (foo (cdr a))))))
Note that the inner function body is identical between the two.
My questions are:
- Is my Y_letrec function commonly used?
- Does it have a well-established name?
Note: The first link above refers to a similar function (in "step 5") as the "applicative-order Y combinator", though I'm having trouble finding an authoritative source for that naming.
UPDATE 28-apr-2013:
I realized that Y_letrec as defined above is very close to but not identical to the Z combinator as defined in Wikipedia. Per Wikipedia, the Z combinator and "call-by-value Y combinator" are the same thing, and it looks like that is indeed the thing that may be more commonly called the "applicative-order Y combinator."
So, what I have above is not the same as the applicative-order Y combinator as usually written, but there is almost certainly a sense in which they're related. Here's how I did the comparison:
Starting with:
Y_letrec = λf . (λx.x x) (λs . (λa . (f ((λx.x x) s)) a))
Apply the inner U:
Y_letrec = λf . (λx.x x) (λs . (λa . (f (s s)) a))
Apply the outer U:
Y_letrec = λf . (λs . (λa . (f (s s)) a)) (λs . (λa . (f (s s)) a))
Rename to match Wikipedia's definition of the Z combinator:
Y_letrec = λf . (λx . (λv . (f (x x)) v)) (λx . (λv . (f (x x)) v))
Compare this to Wikipedia's Z combinator:
Z = λf . (λx . f (λv . ((x x) v))) (λx . f (λv . ((x x) v)))
The salient difference is where the function f
is being applied. Does it matter? Are these two functions equivalent despite this difference?