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:
;; -*- lexical-binding: t; -*-
(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)
(baz))
;; => 42
(baz)
;; => Error symbol variable is void b, duh
This of course expands into:
(let ((b 42))
(defun baz ()
(princ b))
(baz))
(baz)
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)))
(baz)
;; => 42
;; but then
(let ((c 24))
(lexical-let ((b 42))
(defun baz ()
(princ (vector b c)))))
(baz)
;; => Error, obviously, since `lexical-let' forces us to be explicit about our env
;; How about that
(let ((a 24))
(eval
'(let ((b 42))
(defun bazz ()
(princ (vector a b))))
'lexical))
(bazz)
;; => 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?