There probably is a better way to do this with lexical scoping. This is more or less a translation from Rosetta Code:
(defun closure (y)
`(lambda (&rest args) (apply (funcall ',y ',y) args)))
(defun Y (f)
((lambda (x) (funcall x x))
`(lambda (y) (funcall ',f (closure y)))))
(defun factorial (f)
`(lambda (n)
(if (zerop n) 1
(* n (funcall ,f (1- n))))))
(funcall (Y 'factorial) 5) ;; 120
Here's a link to Rosetta code: http://rosettacode.org/wiki/Y_combinator with a bunch of other languages immplementing the same thing. Y-combinator is a construct, from the family of fixed-point combinators. Roughly, the idea is to eliminate the need for implementing recursive functions (recursive functions require more sophistications when you think about how to make them compile / implement in the VM). Y-combinator solves this by allowing one to mechanically translate all functions into non-recursive form, while still allowing for recursion in general.
To be fair, the code above isn't very good, because it will create new functions on each recursive step. This is because until recently, Emacs Lisp didn't have lexical bindings (you couldn't have a function capture its lexical environment), in other words, when the Emacs Lisp function is used outside the scope it was declared, the values of the bound variables will be taken from the function's current scope. In the case above such bound variables are f
in the Y
function and y
in the closure
function. Luckily, those are just symbols designating an existing function, so it is possible to mimic that behaviour using macros.
Now, what Y-combinator does:
Captures the original function into variable f
.
Returns a wrapper function of one argument, which will call f
, when called in its turn, used by Y-combinator to
Return a wrapper function of unbounded number of arguments which will
call the original function passing it all the arguments it was called with.
This structure also dictates you the structure of the function to be used with Y-combinator: it has to take single argument, which must be a function (which is this same function again) and return a function (of any number of arguments) which calls the function inherited from outer scope.
Well, it is known to be a little mind-boggling :)