Aemon Wang
Aemon Wang

Reputation: 31

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"?

Upvotes: 2

Views: 1030

Answers (3)

Rainer Joswig
Rainer Joswig

Reputation: 139411

For comparison with Common Lisp: here you can see that the Common Lisp printer makes the variable look different. One is your max and the other one is an uninterned #:max.

CL-USER 70 > (pprint (macroexpand '(for max from 1 to 10 do (print max))))

(LET ((MAX 1) (#:MAX 10))
  (LOOP WHILE (<= MAX #:MAX) DO (PROGN (PRINT MAX) (INCF MAX))))

If we tell the Common Lisp printer to support printing circular datastructures, then the printer marks where the uninterned symbols are the same: #1=#:MAX is the symbol with a printer label. #1# is then a reference to that labelled thing.

CL-USER 71 > (setf *print-circle* t)
T

CL-USER 72 > (pprint (macroexpand '(for max from 1 to 10 do (print max))))

(LET ((MAX 1) (#1=#:MAX 10))
  (LOOP WHILE (<= MAX #1#) DO (PROGN (PRINT MAX) (INCF MAX))))

But that's Common Lisp, not Emacs Lisp - though they are related, by being both successors to an earlier Lisp dialect.

Upvotes: 0

Joshua Taylor
Joshua Taylor

Reputation: 85913

The short answer is that (make-gensym "max") creates a new symbol whose name is max. You're also using a symbol whose name is max. These have the same name, and thus, in Emacs, print in the same way, but they're not the same symbol. We can test this easily by creating a macro that takes creates a symbol and returns a form that compares it with the macro's argument:

(defmacro test-gensym (arg)
  (let ((max (make-symbol "max")))
    `(eq ',max ',arg)))

If we look at the macroexpansion, we can see that we're comparing two symbols whose names are the same:

(print (macroexpand '(test-gensym max)))
;;=> (eq (quote max) (quote max))

But if we actually run that code, we'll see that the values being compared are not the same:

(test-gensym max)
;;=> nil

Upvotes: 1

Vatine
Vatine

Reputation: 21288

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.

Upvotes: 4

Related Questions