Search code examples
emacslisp-macros

Elisp: Macro expansion at load time with eval of undefined variable


Lisp-rookie here.

My goal is to define a macro that will make the keys of a dotted alist available as variables to access the corresponding values, hence the name »let-dotted-alist«. So what I want is:

(setq foo '((a . "aa") (b . "bb")))

(let-dotted-alist foo
(message (concat a b)))
                           ==> "aabb"

Here's the best I could come up with so far:

(defmacro let-dotted-alist (alist &rest body)
"binds the car of each element of dotted ALIST to the corresponding cdr and makes them available  as variables in BODY."
  `(let ,(nreverse 
      (mapcar    
       (lambda (p) (list (car p) (cdr p))) 
       (eval alist)))
     ,@body))

The problem here is the eval. I need it in order to be able to pass the alist as a variable (foo) rather than a literal, which is the main use case when defining functions. For the macro to work out the code this variable needs to be bound already. Still everywhere I read that the use of eval tends to indicate a flaw in the code? Is there a way around it?

This would be a somewhat academic problem if Emacs 24 hadn't introduced eager macro expansion which wants to expand the macro at load time, when the variable (dotlist in the following example) that should provide the alist is still void:

(defun concat-my-cdrs (dotlist)
  (let-dotted-alist dotlist
            (print (concat a b))))

Depending on how I evaluate this I either get »mapcar: Symbol's value as variable is void: dotlist« or »Eager macro-expansion failure: (void-variable dotlist)«. This makes sense of course, because the variable dotlist is indeed void at load time.

Now before I try to find a workaround along the lines of (locally) disabling eager macro expansion, is there any way to improve on the macro definition to avoid the eval altogether?


Solution

  • I don't believe you can avoid eval, the way the problem is stated: macroexpand let-bindings depending on a variable. The only way not to use eval would be to postpone the variable evaluation until after macroexpand, but it contradicts the requirement of expanding the let bindings. I think I am stating something that is obvious to you.

    So the question apparently is whether your requirements can be changed to eliminate the need for eval. Which would mean either avoiding let-bindings, or making the bindings explicit in macro parameters. It's up to you to decide if it's acceptable, but my personal take is - eval is not so bad as to kill your use case. I would keep eval there. (I did essentially the same recently in Clojure where I needed to bind same local symbols to different values, emulating OCaml functors. That's a digression to explain why I am probably biased towards keeping the way you did it.)

    I might not be aware of some elisp-specific tricks - although even then I'd probably prefer eval to some trickery I've never come across of so far.