0

If I have a function which needs to know if it's calling itself, what is an idiomatic way to check for this situation?

While I can always bind a symbol (with a name that's highly likely to be unique) with let, then check if it's declared in the outer scope, I'm not sure if this is the best way.

Is there a common convention for handling this case?

2
  • 2
    I believe @Basil answered the question. But I think it might help if you added an example use case - e.g., show why you want a function to know if its current invocation is a recursive (or an iterated) one.
    – Drew
    Dec 15 '20 at 4:42
2

One common way to detect recursion without using a global variable is the use of an optional argument:

(defun foo (x &optional recursed)
  "Frobnicate X."
  (unless recursed
    (foo x t)))

You could even hide the optional argument from the function's API:

(defun foo (x &optional recursed)
  "Frobnicate X."
  (declare (advertised-calling-convention (x) "1.0.0"))
  (unless recursed
    (foo x t)))

or:

(defun foo (x &optional recursed)
  "Frobnicate X.
\n(fn X)"
  (unless recursed
    (foo x t)))

(See (info "(elisp) Function Documentation").)

Another way is to create a closure:

(defalias 'foo
  (let (recursed)
    (lambda (x)
      (unless recursed
        (setq recursed t)
        (foo x))))
  "Frobnicate X.")

Of course, there's nothing wrong with using a dynamic variable instead, depending on your needs. For example, the built-in lisp/net/shr.el HTML renderer uses a counter variable that it increments with each recursive call to shr-descend.

If you're looking for something more complicated than just guarding against recursion, then you may want to look at how called-interactively-p uses backtrace-frame, or mapbacktrace.

2
  • I can't use the calling convention method, as I don't have a lot of control of who is calling what/when. Could you show how the foo alias would be used?
    – ideasman42
    Dec 15 '20 at 2:06
  • @ideasman42 The closure is like any old defun, except recursed is nil only the first time it is called. You can try calling it for any value X as (foo X).
    – Basil
    Dec 15 '20 at 2:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.