7

I want to demonstrate my lack of knowledge with an example.

Using the following two macro defintions,

(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 'max))
    `(let ((,var ,init)
           (,tempvar ,final))
       (while (<= ,var ,tempvar)
         ,@body
         (setq ,var (1+ ,var))))))
(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
         (setq ,var (1+ ,var))))))

the first macro shadows any variables named max which may occur in the body and the second does not, which can be showed with the following example:

(let ((max 0)) ;; this variable is shadowed if the first macro defintion is used
  (for i from 1 to 3 do
       (setq square (* i i))
       (if (> square max) 
         (princ (format "\n%d %d" i square)))))

As far as I have learned the evaluation of a macro call works like this:

The macro gets evaluated twice. First the body gets evaluated and returns a form. This form then gets evaluated again.

So far so good.

But if I assume that the macro really returns a chunk of text which happens to be a lisp form, which then gets interpreted, I get a conflict that makes me unable to understand the example above.

What chunk of text does the second macro which uses make-symbol return, so that no shadowing occurs? In my understanding an extreme unlikely random choosen symbol name would make sense.

If I use pp-macroexpand... both macros return the same expansion.

Is someone able to help me out of this confusion?

7
  • 1
    It's not a chunk of text, but a list of symbols. Some of which can be special and therefore never collide with others...
    – wasamasa
    Dec 16, 2015 at 17:49
  • 2
    If you set print-gensym and print-circle to t you will be able to see the difference in the macro expansions.
    – npostavs
    Dec 16, 2015 at 17:50
  • @wasamasa how they are special and what make this actually work is exactly what I want to understand.
    – clemera
    Dec 16, 2015 at 19:13
  • @npostavs Thanks, so the returned form is different and this is just not visible with the default settings.... I see the uninterned symbol gets replaced by #:max. What does that mean ? I'm highly interested in more details.
    – clemera
    Dec 16, 2015 at 19:16
  • 3
    The #: is just a convention of the printer to indicate an uninterned symbol (this is what print-gensym turns on), there is more detail in the manual: (elisp) Creating Symbols.
    – npostavs
    Dec 16, 2015 at 22:34

1 Answer 1

5

As mentioned in the comments, you have to turn on this setting to see how macro-expansion works more precisely. Note that it only changes how macroexpand is displayed, the macros still work the same in either case:

(setq print-gensym t)

Then the first instance expands to (incorrect):

(let ((max 0))
  (let ((i 1)
        (max 3))
    (while (<= i max)
      (setq square (* i i))
      (if (> square max)
          (princ
           (format "\n%d %d" i square)))
      (setq i (1+ i)))))

And the second one expands to (correct):

(let ((max 0))
  (let
      ((i 1)
       (#:max 3))
    (while
        (<= i #:max)
      (setq square
            (* i i))
      (if
          (> square max)
          (princ
           (format "\n%d %d" i square)))
      (setq i
            (1+ i)))))

To further understand the difference, consider evaluating this:

(setq max 10)
;; => 10
(symbol-value 'max)
;; => 10
(symbol-value (make-symbol "max"))
;; => Caught unbound variable #:max, setting it to nil.
(make-symbol "max")
;; => #:max
(eq (make-symbol "max")
    (make-symbol "max"))
;; => nil

As you can see, the result of (make-symbol "max") has no value. This ensures the correctness of the first eval pass on the macro. With the second eval pass, #:max gains a value since it's now bound as a dynamic variable.

4
  • You need also print-circle otherwise you have 2 #:max that are not eq (consider also nested for).
    – npostavs
    Dec 17, 2015 at 14:25
  • Thanks, with your answer and the comments it becomes much clearer to me. But the symbol name remains max right? So I assume there is a way the Interpreter knows not to lookup the uninterned symbol max in the usual symbol table but in another lookup table...
    – clemera
    Dec 18, 2015 at 9:59
  • On the second pass, the symbol max is a let-bound variable: all is fine in that case. In the first case, it's just a unique uninterned symbol - there's no attempt to intern it. Have a look at cl-gensym if it's still not clear.
    – abo-abo
    Dec 18, 2015 at 11:07
  • Thanks for your help. I still have problems with the details: On the second pass there is the let bound max and the #:max and I still don't understand how it works that they are distinguishable. If I call symbol-name on the make-symbol created max it is max, too. So why is the interpreter not confused, what is the mechanism that he knows how to lookup the right value?
    – clemera
    Dec 19, 2015 at 10:01

Your Answer

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

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