From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Nicolas Richard Newsgroups: gmane.emacs.help Subject: Re: macro temp variables Date: Fri, 19 Sep 2014 14:11:02 +0200 Message-ID: <87a95v4xpl.fsf@geodiff-mac3.ulb.ac.be> References: <87fvfnnavc.fsf@ericabrahamsen.net> NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: text/plain X-Trace: ger.gmane.org 1411128549 8626 80.91.229.3 (19 Sep 2014 12:09:09 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Fri, 19 Sep 2014 12:09:09 +0000 (UTC) Cc: help-gnu-emacs@gnu.org To: Eric Abrahamsen Original-X-From: help-gnu-emacs-bounces+geh-help-gnu-emacs=m.gmane.org@gnu.org Fri Sep 19 14:09:02 2014 Return-path: Envelope-to: geh-help-gnu-emacs@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1XUwzi-0003CW-Bd for geh-help-gnu-emacs@m.gmane.org; Fri, 19 Sep 2014 14:08:58 +0200 Original-Received: from localhost ([::1]:57871 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XUwzh-00013J-RJ for geh-help-gnu-emacs@m.gmane.org; Fri, 19 Sep 2014 08:08:57 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:43015) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XUwzO-00012o-FE for help-gnu-emacs@gnu.org; Fri, 19 Sep 2014 08:08:44 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1XUwzG-00028j-Sk for help-gnu-emacs@gnu.org; Fri, 19 Sep 2014 08:08:38 -0400 Original-Received: from mxin.ulb.ac.be ([164.15.128.112]:45879) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XUwzG-00027h-B7 for help-gnu-emacs@gnu.org; Fri, 19 Sep 2014 08:08:30 -0400 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AiwKAHwbHFSkD4Xx/2dsb2JhbABXCYQ3tTIGm3sBgRgBeYQEAQEEcgcQCyElDwEESRMYA4gOARSuNIxDAYgFAReGCYIngWWFB1sHhEsFjziVBY4Xg2A7L4EGJIEgAQEB Original-Received: from mathsrv4.ulb.ac.be (HELO geodiff-mac3.ulb.ac.be) ([164.15.133.241]) by smtp.ulb.ac.be with ESMTP; 19 Sep 2014 14:08:21 +0200 In-Reply-To: <87fvfnnavc.fsf@ericabrahamsen.net> (Eric Abrahamsen's message of "Fri, 19 Sep 2014 18:49:27 +0800") User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.3.93 (gnu/linux) X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 164.15.128.112 X-BeenThere: help-gnu-emacs@gnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: Users list for the GNU Emacs text editor List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: help-gnu-emacs-bounces+geh-help-gnu-emacs=m.gmane.org@gnu.org Original-Sender: help-gnu-emacs-bounces+geh-help-gnu-emacs=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.help:100023 Archived-At: Eric Abrahamsen 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