4

So I define a macro (defmacro macro-print (str) (print "hi") and then run

(let ((i 0))
  (while (< i 3)
   (macro-print i)
   (setq i (+ 1 i)))

according to the Elisp manual, the interpreter should evaluate (macro-print i) three times, and hence I should see "hi" printed three times, yet, I only see it once. This makes me think that the form is somehow being compiled and the macro expanded during compilation. This seems to be the same thing that happens in SBCL. Running the same experiment there, I see one "hi" when the evaluator mode is set to compile, and three "hi"s when it is set to interpret. As I understand the Hyperspec, the interpret result is the correct behavior when evaluating Common Lisp (the compile behavior I am not sure, I have not read that part of the Hyperspec yet).

Elisp does not have a hyperspec, but the manual suggests that I should be seeing interpret-like behavior, why then do I see compile-type behavior? Shouldn't the same piece of code behave the same way regardless of compilation/interpretation?

EDIT To address Drew's feedback:

For 1,2) I read the Elisp manual and according to the evaluation rules of 'while', every time the condition form evaluates to true, we evaluate the body forms. In my example, one of the body forms is a macro, which according to the macro evaluation rules, must be expanded whenever evaluated. This means the macro should be expanded 3 times in the loop (each time printing "hi"), but when run, I only see "hi" once. Why is the macro being expanded just once? Is it that when we expand it, we replace it in the 'while' form? The connection I see to Common Lisp is that this is the CL behavior for "compile" mode where we expand all macros first as detailed in the Hyperspec, and then evaluate the form. Does such an interpreted vs. compiled distinction exist in Elisp?

For 3), I know this isn't the right way to use macros, but it is the easiest example I could find that illustrates my confusion with the Elisp evaluation model.

4
  • When I run your code (after adding enough close-parens to make it correct), "hi" does get printed three times to the message buffer. What do you see in the message buffer (C-h e to see it)? Nov 27, 2022 at 19:16
  • Didn’t we already answer this same question a few hours ago, including the same macro with (print "hi") in it?
    – db48x
    Nov 27, 2022 at 19:21
  • @FranBurstall I only get "hi" once, even in the message buffer, and even after re-directing with the optional stream argument to print. Is there some setting I am missing?
    – Isabella
    Nov 27, 2022 at 20:18
  • 1
    @db48x I asked this on StackOverflow but for Common Lisp. In that case, there are two settings, a compile one, which behaves like you described in your answer, and an interpreted one, which behaves like I describe in my question. Fran Burstall's comment above is confusing though, why is he getting the other behavior?
    – Isabella
    Nov 27, 2022 at 20:24

3 Answers 3

1

If I go to the *scratch* buffer and evaluates this form with eval-last-sexp, using C-x C-e:

(eval 
  '(let ((i 0))
    (while (< i 3)
     (macro-print i)
     (setq i (+ 1 i)))))

Then the *Messages* buffer contains:

"hi"

"hi"

"hi"

However, if I evaluate the unquoted form with C-x C-e, I only see one "hi" message. It turns out that eval-last-sexp ends up calling macroexpand-all:

(defun elisp--eval-last-sexp (eval-last-sexp-arg-internal)
  "Evaluate sexp before point; print value in the echo area.
If EVAL-LAST-SEXP-ARG-INTERNAL is non-nil, print output into
current buffer.  If EVAL-LAST-SEXP-ARG-INTERNAL is `0', print
output with no limit on the length and level of lists, and
include additional formats for integers \(octal, hexadecimal, and
character)."
  (pcase-let*
      ((`(,insert-value ,no-truncate ,char-print-limit)
        (eval-expression-get-print-arguments eval-last-sexp-arg-internal)))
    ;; Setup the lexical environment if lexical-binding is enabled.
    (elisp--eval-last-sexp-print-value
     (eval (macroexpand-all
            (eval-sexp-add-defvars
             (elisp--eval-defun-1 (macroexpand (elisp--preceding-sexp)))))
           lexical-binding)
     (if insert-value (current-buffer) t) no-truncate char-print-limit)))

The first example above contained a quoted form, which is why the macro was not expanded by eval-last-sexp. Then, the eval function was called on that unquoted form, following the semantics described in the manual.

In the second case, the whole form is expanded once, which prints "hi", then the result of the expansion is evaluated.

The conclusion is that the evaluation model described in the manual is correct: evaluating a macro is done when encountering it, as many times as the form is evaluated. But, also, you are not expected to rely on that particular behavior because:

[...] Emacs tries to expand macros when loading an uncompiled Lisp file. This is not always possible, but if it is, it speeds up subsequent execution.

More generally, macros are designed to be expandable outside of an evaluation context, as part of a preprocessing step. This happens for example during compilation, or as we just saw, when calling the evaluation command from the editor. It is generally bad to have code that behaves differently whether or not it is processed first by macroexpand-all.

In SBCL, even eval tries to compile/macroexpand everything when in :compile mode, and unfortunately being too good at compiling code might give a worse debugging experience. The :interpret mode is a way to have a simpler model of evaluation that, for example, allows the user to redefine a macro while debugging and have it expanded differently the next time it is evaluated.

1
  • 1
    The manual isn't as formal as the Hyperspec, maybe that's why I was expecting something more explicit, but this clears up my confusion.
    – Isabella
    Nov 28, 2022 at 18:03
4

Macro expansion happens before evaluation. If you want to see what some code expands to, use macroexpand-1 or macroexpand-all. For example, with the macro defined as given in your question, evaluating this code in the *scratch* buffer:

(macroexpand-all '(let ((i 0))
                    (while (< i 3)
                      (macro-print i)
                      (setq i (+ 1 i)))))

results in this:

"hi"
(let ((i 0)) (while (< i 3) "hi" (setq i (+ 1 i))))

As you can see, it printed "hi", which went to the current buffer. This happens a single time because there is a single call to the macro-print macro in the code to be expanded. It then returns the code will all calls to macros replaced by whatever the macro returned, which in this case was just the string "hi". That "hi" goes in the exact place of the call to macro-print, inside the body of the while loop.

Sure, if you run this code the while loop is going to cause that string to be evaluated three times, but evaluating a string just gives you the string. The while loop isn’t going to do anything with it.

If you redefine the macro with a quote in it, like this: (defmacro macro-print (str) '(print "hi")), then the macro will return some code instead of just the string. Expanding the macros again gives you this:

(let ((i 0)) (while (< i 3) (print "hi") (setq i (+ 1 i))))

You can see that the code returned by the macro, (print "hi"), is now inserted into the body of the loop where it can be called three times.

In Emacs Lisp, the behavior should be identical whether the code is interpreted or compiled, as both interpretation and compilation happen after macro expansion.

Different Lisps may have slightly different implementations, but it is customary to have some facility for interactively expanding macros so that you can see what is going on.

1
  • the code behaves differently when going through eval directly
    – coredump
    Nov 28, 2022 at 13:58
0

Here's some feedback, to hopefully help. But the question should be closed as unclear, or else you should edit it to some part of what you wanted to ask (but don't evolve the question to something else).

  1. The question doesn't seem to be about compiling vs interpreting.

  2. The question doesn't seem to be about Common Lisp.

  3. You don't want a macro for this; you want a function.

If you're interested in learning about Elisp macros, check the Elisp manual.

A macro "call" -- the entire sexp (e.g. (macroprint i)) -- is expanded according to the function the defmacro defines -- and the result of that expansion is then evaluated. The macro call is, in effect, replaced by the result of that evaluation.

In this case, that function expands the macro sexp ((macroprint i)) to (print "hi")), and that expansion ((print "hi") is evaluated to "hi". So "hi" replaces the macro call in your code.

1
  • 2
    I read the Elisp manual and according to the evaluation rules of 'while', every time the condition form evaluates to true, we evaluate the body forms. In my example, one of the body forms is a macro, which according to the macro evaluation rules, must be expanded whenever evaluated. This means the macro should be expanded 3 times in the loop (each time printing "hi"), but when run, I only see "hi" once. Why is the macro being expanded just once? The connection I see to common lisp is that this is the CL behavior for "compile" mode, does such a distinction exist in Elisp?
    – Isabella
    Nov 27, 2022 at 18:14

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.