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?
First, I'll mention that eager-macroexpansion does not introduce a new problem: the same issue shows up in earlier Emacsen if you try to byte-compile your file.
As for avoiding eval
, you kind of can, by using something else which uses eval
, e.g. cl-progv
:
(defmacro let-dotted-alist (alist &rest body)
(macroexp-let2 nil alist alist
`(cl-progv (mapcar #'car ,alist)
(mapcar #'cdr ,alist)
,@body)))
But note that the semantics is a bit different: the variables can be only dynamically scoped rather than lexically scoped, because the list of variables will be known only at run-time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With