Search code examples
emacslispelisp

elisp macro expansion local variable


I get touch on elisp recently and try to understand the how elisp macro works. The GNU tutorial has a chapter Surprising-local-Vars for macro local variable and I am get confused about how the macro expansion works.

(defmacro for (var from init to final do &rest body)
  "Execute a simple for loop: (for i from 1 to 10 do (print i))."
  (let ((tempvar (make-symbol "max")))
    `(let ((,var ,init)
           (,tempvar ,final))
       (while (<= ,var ,tempvar)
         ,@body
         (inc ,var)))))

There are two let forms. the first one

(let ((tempvar (make-symbol "max")))

doesn't has the backquote, which will get evaluated at macro expand phrase, therefor the uninterned symbol "max" will get created only at that phrase. And the uninterned symbol "max" will get lost at runtime, it should not work right?

But actually, it works well. I tried the following:

(for max from 1 to 10 do (print max))

And the its expansion as following:

(macroexpand '(for max from 1 to 10 do (print max)))

(let ((max 1) (max 10)) (while (<= max max) (print max) (setq max (+ 1 max))))

two max symbol here, one bound to 1, another bound to 10, the while form expression has two max symbol.

(while (<= max max)

how does the while form resolve the two different the symbol "max"?


Solution

  • You are looking at print-names, not symbol identities. You have two symbols, both with the "print name" max, but different identities. Normally, I would recommend using gensym rather than make-symbol, but it really doesn't matter much which way it's done.

    Think of a symbol as being a pointer to a small structure, having a variety of values stored in it. One of these is a "name", when a symbol is interned, this is placed in a special structure, so you can find the symbol by its name. What you're seeing is an interned symbol with the name max and an un-interned symbol with the name max. They are different symbols (that is, two structures and the pointers are thus different), but when you look just at a printed representation, this is not obvious.

    A quick demonstration, fresh out of an emacs "scratch" buffer:

    (defmacro demo (sym)
      (let ((tmp (make-symbol "max")))
        `(progn
           (message "%s" (list ',tmp ',sym))
           (eql ',tmp ',sym))))
    
    demo
    
    (macroexpand '(demo max))
    (progn (message "%s" (list (quote max) (quote max))) (eql (quote max) (quote max)))
    
    (demo max)
    nil
    

    If you paste the text that results from the macro expansion and evaluate it, you'll see that you get t instead of nil, because during the reading of the expression, you end up with the same symbol.