0

From the Question How do I pass a function as a parameter to in elisp? I know how to pass a function as a parameter to a function. But we need to go deeper...

Lame movie quotes aside, I want to have a function, which takes a function as a parameter and is able to call itself [again passing the function which it took as parameter]. Consider this snippet:

(defun dummy ()
  (message "Dummy"))

(defun func1 (func)
  (funcall func))

(defun func2 (func arg)
  (message "arg = %s" arg)
  (funcall func)
  (func2 'func (- arg 1)))

Calling (func1 'dummy) yields the expected output:

Dummy    
"Dummy"

Calling (func2 'dummy 4) results in an error message:

arg = 4
Dummy
arg = 3
funcall: Symbol's function definition is void: func

I had expected four calls to dummy, yet the second iteration of func2 seems to have lost its knowledge of the function passed to the first iteration (and passed on from there). Any help is much appreciated!

Community
  • 1
  • 1
elemakil
  • 3,681
  • 28
  • 53

3 Answers3

4

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:

  1. Captures the original function into variable f.

  2. Returns a wrapper function of one argument, which will call f, when called in its turn, used by Y-combinator to

  3. Return a wrapper function of unbounded number of arguments which will

  4. 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 :)

  • This is great! I don't understand a thing (yet) but it looks really cool :) Could you explain (or maybe give a link to an explanation?) for an `elisp` learner? – elemakil May 29 '13 at 21:49
1

That's because you're trying to call the function func not the function dummy.

(Hence the error "Symbol's function definition is void: func".)

You want:

(func2 func (- arg 1)))

not:

(func2 'func (- arg 1)))
phils
  • 71,335
  • 11
  • 153
  • 198
1
  1. You do not need to quote func in the func2 call
  2. You are missing a recursion termination condition in func2

Here is what works for me:

(defun func2 (func arg)
  (message "arg = %s" arg)
  (funcall func)
  (when (plusp arg)
    (func2 func (- arg 1))))
sds
  • 58,617
  • 29
  • 161
  • 278