Today I ran head first into lexical vs dynamic scope in Emacs Lisp. Gist of it is I have a macro defined in lexical scope that expands into a `defun'. Let's say something simple like this:

(defmacro bazmacro (a)
   `(defun baz ()
      (princ ,a)))

By chance I forgot to enable lexical scope in my tests file and bafflement followed:

;; no lexical scope here
(let ((b 42))
   (bazmacro b)
;; => 42
;; => Error symbol variable is void b, duh

This of course expands into:

(let ((b 42))
   (defun baz ()
     (princ b))

and after sufficient amount of head scratching and experimentation you figure fml the above defun doesn't create a closure! Wat! Wait, have I forgotten to enable lexical scope?

In a language like Elisp where dynamic and lexical scope co-exist like they do that raises some interesting questions. Strong case can be made that the above is by design, and I for one concur. But I know for a fact as that library designer that wasn't my intention at all. Of course, the end user must know what they're doing and all that. Leaving questions of right or wrong, good idea or bad idea aside, I had the following thought.

Could I hijack the scope at the call-site just for the extent of my macro body? This led me to the following snippets:

(lexical-let ((b 42))
   (defun baz ()
     (princ b)))
;; => 42

;; but then
(let ((c 24))
  (lexical-let ((b 42))
    (defun baz ()
      (princ (vector b c)))))
;; => Error, obviously, since `lexical-let' forces us to be explicit about our env

;; How about that
(let ((a 24))
   '(let ((b 42))
      (defun bazz ()
        (princ (vector a b))))
;; => Error, well that's disappointing

In lexical scope I could create a temporary closure with lambda, extract its environment and pass it to the above eval, so that bazz gets to share said environment (feels almost like being in Racket with its syntax objects that carry their lexical env). Having read relevant INFO sections, the way dynamic scope works is by setting symbol's value cells and maintaining a simple stack discipline. So, the closure trick would only work in lexical scope, sadly.

Question: is there a way to get at whatever stands for the current environment while in dynamic scope, convert it into an lexical env alist to be used in eval? Or is there a better tried and tested way?


This is arguably a duplicate of How to avoid use of `lexical-let`, and there are many different questions here.

While you may be able to hack something to get some code to sometimes work, I don't recommend you go there: by the time we get to your macro, the surrounding variable has already been defined as dynamically-scoped so it's too late to really fix it (any "fix" will be a messy workaround with various unintended corner case side-effects). Better signal an error in your macro if it's used in a non-lexical-binding context:

(defmacro ... (...)
  (if (not lexical-binding)
      (error "Macro ... only works in lexical-binding")

Also, you may like to M-x report-emacs-bug and request some help from Emacs to avoid such accidental use of dynamic binding: I've suggested to use lexical-binding by default in various contexts (e.g. M-:, *scratch*, and ielm) but the current maintainers do not seem to be aware of the problems you're experiencing, so bug-report from actual users would help raise awareness.

  • 1
    It does seem like this is essentially a duplicate. Should it be closed as such?
    – Drew
    Nov 19 '18 at 15:10
  • While the question you refer to above does indeed share some context with mine, I do not see it as duplicate. First, I make no judgement as to whether this behavior is buggy or not. Indeed, I do not see it as buggy: the interplay between dynamic and lexical while tricky has particular semantics which I don't believe is being violated here. Second, my question is more general. Is there a way to get at the entire environment while in dynamic scope and manipulate it as a data structure, so that, for instance, I could create a local lexical environment.
    – zeRusski
    Nov 19 '18 at 17:17
  • You can get the entire dynamic-scope environment with mapatoms ;-)
    – Stefan
    Nov 19 '18 at 21:37

Your Answer

