help-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: macro temp variables


From: Nicolas Richard
Subject: Re: macro temp variables
Date: Fri, 19 Sep 2014 14:11:02 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.3.93 (gnu/linux)

Eric Abrahamsen <address@hidden> writes:
> I've never actually needed to write a macro that provided temporary
> local variables, and consequently am not very good at it. Despite having
> read the docs and basically followed the examples there, my attempt is
> producing errors.

(Nota : you can skip straight to my solution if you prefer)

Let's recap how macros work: it is supposed to return code that will
then be evaluated. This is a two step process: code is created during an
expansion phase (evaluation time), then evaluated (run time).

Let's write a macro (this is not good):
(defmacro yf/test (code)
  (message "Evaluating your code gives: %s. Yes, it gives: %s"
  code
  code))

Now try it:
(yf/test 4) => "Evaluating your code gives: 4. Yes, it gives: 4"
Great !

(yf/test (+ 2 2)) => "Evaluating your code gives: (+ 2 2). Yes, it
gives: (+ 2 2)"
Ah. not so great.

The message is in fact shown during expansion time, not runtime. To see
this, add another return value after the call to message :
(defmacro yf/test (code)
  (message "Evaluating your code gives: %s. Yes, it gives: %s"
  code
  code)
  nil)
(yf/test 4) => nil

The returned value is nil (because the code generated by the macro is
that: nil, and evaluating nil gives nil), but you can see the message in
the *Messages* buffer.

Ok, so let's fix it (still not good):

(defmacro yf/test (code)
  '(message "Evaluating your code gives: %s. Yes, it gives: %s"
  code
  code))

(yf/test 4) now makes an error (void-variable code). Ouch. Why ? Because
"code" is a symbol defined at expansion time, not at runtime. We want
"code" to be evaluated earlier.

(defmacro yf/test (code)
  `(message "Evaluating your code gives: %s. Yes, it gives: %s"
  ,code
  ,code))

Now our macro will expand to something that will display a message when
evaluated. Looks better :

Evaluate:
(yf/test (+ 2 2)) => "Evaluating your code gives: 4. Yes, it gives: 4"

Wonderful.

But now let's try this
(let ((a 0)) (yf/test (setq a (+ a 1))))
=> "Evaluating your code gives: 1. Yes, it gives: 2"

Now why this happens : because the macro call
(yf/test (setq a (+ a 1)))
expands to this:

(message "Evaluating your code gives: %s. Yes, it gives: %s"
         (setq a
               (+ a 1))
         (setq a
               (+ a 1)))

(you can use M-x pp-macroexpand-last-sexp to see it)

Ok, so what we might do is this :
(defmacro yf/test (code)
  `(let ((intcode ,code))
     (message "Evaluating your code gives: %s. Yes, it gives: %s"
              intcode
              intcode)))

so whatever is contained in the 'code' argument will be evaluated once,
its value will be bound to 'intcode' with a let form, and that value is
used without re-evaluating code. In this macro, 'code' has a meaning
during expansion time, and intcode has a meaning at runtime (when the
let form is evaluated).

(yf/test (+ 2 2))
 => "Evaluating your code gives: 4. Yes, it gives: 4"

Looks promising. What's wrong with that ? Well, not too much. Consider
this:
(defmacro yf/test (code morecode)
  `(let ((intcode ,code))
     (message "Evaluating your code gives: %s. Yes, it gives: %s. More code 
gives: %s"
              intcode
              intcode
              ,morecode)))

(yf/test (+ 2 2) (+ 3 3))
 => "Evaluating your code gives: 4. Yes, it gives: 4. More code gives: 6"

Ok, we expected that.

But:
(let ((intcode 'foo)) (yf/test (+ 2 2) intcode))
 => "Evaluating your code gives: 4. Yes, it gives: 4. More code gives: 4"

This might be unexpected : the name of what is meant as a temporary
binding internal to the macro (intcode), in fact supersedes a binding in
the code using the macro. To avoid this we make use an uninterned symbol:

(defmacro yf/test (code morecode)
  (let ((intcode (make-symbol "`intcode' or any name will do")))
    `(let ((,intcode ,code)) ;; in the code generated, the let form
                             ;; will bind the value of evaluating 'code'
                             ;; to an uninterned symbol.
       (message "Evaluating your code gives: %s. Yes, it gives: %s. More code 
gives: %s"
                ,intcode
                ,intcode
                ,morecode))))

And now:
(let ((intcode 'foo)) (yf/test (+ 2 2) intcode))
 => "Evaluating your code gives: 4. Yes, it gives: 4. More code gives: foo"

Tadaa ! Ok, so the relation to your problem: while it might be
unexpected for a name from the macro internals to be available during
runtime, in your case, you want this behaviour : you want the symbols
tree, head, item, todo and tags to be available to the code in the
,@body.

So, fixing this and an oddity wrt org-narrow-to-subtree in your code, I
got :

(defmacro org-iter-headings (&rest body)
  (declare (indent 0))
  `(save-restriction
     (org-narrow-to-subtree) ;; this has no argument
     (outline-next-heading)             ; Get off the parent heading.
     (let ((tree (org-element-parse-buffer)))
       (org-element-map tree 'headline
         (lambda (head)
           ;; we do not need to call (org-element-at-point) do we ?
           (let ((item (org-element-property :raw-value head))
                 (todo (cons
                        (org-element-property :todo-type head)
                        (org-element-property :todo-keyword head)))
                 (tags (org-element-property :tags head))
                 (body-pars (org-element-map head 'paragraph 'identity)))
             ,@body))))))

Gee, that was much longer than what I expected. I hope it was clear,
though.

-- 
Nicolas Richard



reply via email to

[Prev in Thread] Current Thread [Next in Thread]