2

I have a macro which unpacks a list into variables, as if by setq:

(unpack (a b c) '(1 2 3))

is roughly comparable to

(setq a 1)
(setq b 2)
(setq c 3)

The way I did that initially was by having the macro expand to a dolist on the variable names, where each iteration did one (setq variable-name (car the-list)) and manually stepped through the value list with (setq the-list (cdr the-list)):

(defmacro unpack (names list)
    `(let ((--unpack-- ,list))
        (dolist (name ',names)
            (eval (list 'setq name '(car --unpack--)))
            (setq --unpack-- (cdr --unpack--)))))

But this had a surprising bug: if I used unpack in my init file, or evaluated calls to unpack with eval-last-sexp in a buffer, it worked fine, but if I evaluated calls to unpack in the minibuffer during eval-expression (M-:), it failed with Lisp error: (void-variable --unpack--).

For example, M-: (unpack (test1 test2) '(1 2)) RET leads to:

Debugger entered--Lisp error: (void-variable --unpack--)
  (car --unpack--)
  (setq test1 (car --unpack--))
  eval((setq test1 (car --unpack--)))
  (let ((name (car --dolist-tail--))) (eval (list 'setq name '(car --unpack--))) (setq --unpack-- (cdr --unpack--)) (setq --dolist-tail-- (cdr --dolist-tail--)))
  (while --dolist-tail-- (let ((name (car --dolist-tail--))) (eval (list 'setq name '(car --unpack--))) (setq --unpack-- (cdr --unpack--)) (setq --dolist-tail-- (cdr --dolist-tail--))))
  (let ((--dolist-tail-- '(test1 test2))) (while --dolist-tail-- (let ((name (car --dolist-tail--))) (eval (list 'setq name '(car --unpack--))) (setq --unpack-- (cdr --unpack--)) (setq --dolist-tail-- (cdr --dolist-tail--)))))
  (let ((--unpack-- '(1 2))) (let ((--dolist-tail-- '(test1 test2))) (while --dolist-tail-- (let ((name (car --dolist-tail--))) (eval (list 'setq name '(car --unpack--))) (setq --unpack-- (cdr --unpack--)) (setq --dolist-tail-- (cdr --dolist-tail--))))))
  eval-expression((unpack (test1 test2) '(1 2)) nil nil 127)
  funcall-interactively(eval-expression (unpack (test1 test2) '(1 2)) nil nil 127)
  command-execute(eval-expression)

Here's another variant of unpack which doesn't have this problem: instead of building a dolist form, I do the dolist in the macro itself and build one (setq ...) form:

(defmacro unpack (names list)
    (let* ((setq-form (list 'setq))
           (last-cons setq-form)
           (cdr-chain '--unpack--))
        (dolist (name names)
            (setcdr last-cons (list name (list 'car cdr-chain)))
            (setq cdr-chain (list 'cdr cdr-chain))
            (setq last-cons (cddr last-cons)))
        `(let ((--unpack-- ,list)) ,setq-form nil)))

But I don't understand why the first one works fine in normal usage yet fails in M-:.

3
  • Maybe yours is an exercise and my comment is useless but you know you can write (setq a 1 b 2 c 3), right? If you have the lists of names and values then you could use (seq-mapn #'set '(a b c) '(1 2 3)) (manual). Commented Sep 7, 2023 at 18:08
  • 1
    I just discovered that there's also seq-setq: (seq-setq (a b c) '(1 2 3)) works like unpack. Commented Sep 7, 2023 at 18:17
  • @ArchStanton re: seq-setq: ah, excellent, I knew there must've been a built-in equivalent somewhere. (I knew about pcase, but found it off-puttingly complex for my needs. I see that seq-setq uses pcase internally, but at least it hides all that complexity so I don't have to deal with it behind an equally clear and self-descriptive interface. And it handles the &rest case!) Thanks!
    – mtraceur
    Commented Sep 8, 2023 at 2:58

1 Answer 1

5

Error because you use eval.

(eval FORM &optional LEXICAL)

Evaluate FORM and return its value.
If LEXICAL is t, evaluate using lexical scoping.
LEXICAL can also be an actual lexical environment, in the form of an alist mapping symbols to their value.

You can write these:

(eval 'teeest '((teeest . 42)))
;; => 42

(eval '(let ((teeest 43))
         teeest) t)
;; => 43

But you shouldn't expect

(let ((teeest 44))
  (eval 'teeest))

to return 44 if you don't mark teeest as a special variable (e.g., by using defvar).

This is because

Note that unlike dynamic variables which are tied to the symbol object itself, the relationship between lexical variables and symbols is only present in the interpreter (or compiler).
Therefore, functions which take a symbol argument (like symbol-value, boundp, and set) can only retrieve or modify a variable’s dynamic binding (i.e., the contents of its symbol’s value cell).

3
  • Excellent, thanks! I had a strong suspicion it was due to the eval, but I didn't really understand why.
    – mtraceur
    Commented Sep 8, 2023 at 2:40
  • Although... I still can't say I fully understand why it seemed to work in some cases... I didn't have any defvar, defconst, or setq outside of (let ...) for --unpack-- anywhere in my code that I'm aware of.
    – mtraceur
    Commented Sep 8, 2023 at 3:06
  • @mtraceur: A little difficult for me to speculate as to why, because I haven't actually experienced your Emacs and don't know exactly what's going on before and after.
    – shynur
    Commented Sep 9, 2023 at 13:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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