* macro temp variables @ 2014-09-19 10:49 Eric Abrahamsen 2014-09-19 12:11 ` Nicolas Richard 0 siblings, 1 reply; 8+ messages in thread From: Eric Abrahamsen @ 2014-09-19 10:49 UTC (permalink / raw) To: help-gnu-emacs 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. The idea with the below is to make a macro that iterates over Org headlines, and runs the body once for each headline: for each run, a handful of temporary variables should be bound to various bits of the headline. It should be fairly clear from looking at it. "tree" should be bound once, at the top level of the call. All the other make-symbol variables should be re-bound with each pass of org-element-map. I tested this with a little stub call that tried to access the 'todo symbol, and that gets me "symbol's value as variable is void" for 'todo. I tried replacing the inner "setq" series with a let*, and got the same result. Clearly this is just not the way you do it, but I've tried several different things and nothing works. Am I supposed to be using nested back-quotes? Can someone tell me how to fix this? (defmacro org-iter-headings (&rest body) (declare (indent 0)) (let ((tree (make-symbol "tree")) (head (make-symbol "head")) (item (make-symbol "item")) (todo (make-symbol "todo")) (tags (make-symbol "tags")) (body-pars (make-symbol "body"))) `(save-restriction (org-narrow-to-subtree (outline-next-heading) ; Get off the parent heading. (let ((,tree (org-element-parse-buffer))) (org-element-map ,tree 'headline (lambda (h) (setq ,head (org-element-at-point) ,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))))))) ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: macro temp variables 2014-09-19 10:49 macro temp variables Eric Abrahamsen @ 2014-09-19 12:11 ` Nicolas Richard 2014-09-19 15:48 ` Nicolas Richard 0 siblings, 1 reply; 8+ messages in thread From: Nicolas Richard @ 2014-09-19 12:11 UTC (permalink / raw) To: Eric Abrahamsen; +Cc: help-gnu-emacs Eric Abrahamsen <eric@ericabrahamsen.net> 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 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: macro temp variables 2014-09-19 12:11 ` Nicolas Richard @ 2014-09-19 15:48 ` Nicolas Richard 0 siblings, 0 replies; 8+ messages in thread From: Nicolas Richard @ 2014-09-19 15:48 UTC (permalink / raw) To: Nicolas Richard; +Cc: Eric Abrahamsen, help-gnu-emacs Nicolas Richard writes: > 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). ^^^^^^^^^^ I meant expansion, there, sorry. -- Nicolas Richard ^ permalink raw reply [flat|nested] 8+ messages in thread
[parent not found: <mailman.9181.1411123560.1147.help-gnu-emacs@gnu.org>]
* Re: macro temp variables [not found] <mailman.9181.1411123560.1147.help-gnu-emacs@gnu.org> @ 2014-09-19 17:33 ` Pascal J. Bourguignon 2014-09-19 17:45 ` Stefan Monnier ` (3 more replies) 0 siblings, 4 replies; 8+ messages in thread From: Pascal J. Bourguignon @ 2014-09-19 17:33 UTC (permalink / raw) To: help-gnu-emacs Eric Abrahamsen <eric@ericabrahamsen.net> 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. > > The idea with the below is to make a macro that iterates over Org > headlines, and runs the body once for each headline: for each run, a > handful of temporary variables should be bound to various bits of the > headline. > > It should be fairly clear from looking at it. "tree" should be bound > once, at the top level of the call. All the other make-symbol variables > should be re-bound with each pass of org-element-map. > > I tested this with a little stub call that tried to access the 'todo > symbol, and that gets me "symbol's value as variable is void" for 'todo. > I tried replacing the inner "setq" series with a let*, and got the same > result. Clearly this is just not the way you do it, but I've tried > several different things and nothing works. Am I supposed to be using > nested back-quotes? Can someone tell me how to fix this? > > (defmacro org-iter-headings (&rest body) > (declare (indent 0)) > (let ((tree (make-symbol "tree")) > (head (make-symbol "head")) > (item (make-symbol "item")) > (todo (make-symbol "todo")) > (tags (make-symbol "tags")) > (body-pars (make-symbol "body"))) > `(save-restriction > (org-narrow-to-subtree > (outline-next-heading) ; Get off the parent heading. > (let ((,tree (org-element-parse-buffer))) > (org-element-map ,tree 'headline > (lambda (h) > (setq ,head (org-element-at-point) > ,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))))))) You should indeed better use a let for rather than setq, but the problem is not here. The problem is that you want your body to access those variables. So the body must know their names. But you are computing new names that are uninterned, and therefore unaccessible. Therefore there's no way to access those temporary variables, from the body. Only code generated by your macro could access those variables (since the macro has their name stored in its head .. tags variables. In general, when you want to provide access to variables by the body, you must let the user name those variables. (setf lexical-binding t) ; always (require 'cl) ; always (defmacro* org-iterating-headings ((head item todo tags body-pars) &rest body) `(call-org-iterating-headings (lambda (,head ,item ,todo ,tags ,body-pars) ,@body))) You could write also it with emacs defmacro, but you would have to write: (defmacro org-iterating-headings (vars &rest body) (destructuring-bind (head item todo tags body-pars) vars `(call-org-iterating-headings (lambda (,head ,item ,todo ,tags ,body-pars) ,@body)))) (defun call-org-iterating-headings (thunk) (save-restriction (org-narrow-to-subtree (outline-next-heading) ; Get off the parent heading. (let ((tree (org-element-parse-buffer))) (org-element-map tree 'headline (lambda (h) (let* ((head (org-element-at-point)) (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))) (funcall thunk head item todo tags body-pars)))))))) You would use it as: (org-iterating-headings (h i to ta ps) (do-something-with-head h) (mapc (function do-something-else-with-par) ps)) which expands to: (pp (macroexpand '(org-iterating-headings (h i to ta ps) (do-something-with-head h) (mapc (function do-something-else-with-par) ps)))) (call-org-iterating-headings (lambda (h i to ta ps) (do-something-with-head h) (mapc #'do-something-else-with-par ps))) -- __Pascal Bourguignon__ http://www.informatimago.com/ “The factory of the future will have only two employees, a man and a dog. The man will be there to feed the dog. The dog will be there to keep the man from touching the equipment.” -- Carl Bass CEO Autodesk ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: macro temp variables 2014-09-19 17:33 ` Pascal J. Bourguignon @ 2014-09-19 17:45 ` Stefan Monnier [not found] ` <mailman.9214.1411148764.1147.help-gnu-emacs@gnu.org> ` (2 subsequent siblings) 3 siblings, 0 replies; 8+ messages in thread From: Stefan Monnier @ 2014-09-19 17:45 UTC (permalink / raw) To: help-gnu-emacs > (setf lexical-binding t) ; always If someone wants to do that, more power to him, but I *strongly* recommend *against* doing the above. The way to set the lexical-binding variable is by adding "-*- lexical-binding:t -*-" somewhere on the first line of your file. Stefan ^ permalink raw reply [flat|nested] 8+ messages in thread
[parent not found: <mailman.9214.1411148764.1147.help-gnu-emacs@gnu.org>]
* Re: macro temp variables [not found] ` <mailman.9214.1411148764.1147.help-gnu-emacs@gnu.org> @ 2014-09-19 18:51 ` Pascal J. Bourguignon 0 siblings, 0 replies; 8+ messages in thread From: Pascal J. Bourguignon @ 2014-09-19 18:51 UTC (permalink / raw) To: help-gnu-emacs Stefan Monnier <monnier@iro.umontreal.ca> writes: >> (setf lexical-binding t) ; always > > If someone wants to do that, more power to him, but I *strongly* > recommend *against* doing the above. > > The way to set the lexical-binding variable is by adding > "-*- lexical-binding:t -*-" somewhere on the first line of your file. Indeed. (setf lexical-binding t) is for when you're working in pre-existing files, perhaps not even in emacs-lisp-mode, like gnus message buffers ;-) -- __Pascal Bourguignon__ http://www.informatimago.com/ “The factory of the future will have only two employees, a man and a dog. The man will be there to feed the dog. The dog will be there to keep the man from touching the equipment.” -- Carl Bass CEO Autodesk ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: macro temp variables 2014-09-19 17:33 ` Pascal J. Bourguignon 2014-09-19 17:45 ` Stefan Monnier [not found] ` <mailman.9214.1411148764.1147.help-gnu-emacs@gnu.org> @ 2014-09-21 4:37 ` Eric Abrahamsen [not found] ` <mailman.9302.1411299483.1147.help-gnu-emacs@gnu.org> 3 siblings, 0 replies; 8+ messages in thread From: Eric Abrahamsen @ 2014-09-21 4:37 UTC (permalink / raw) To: help-gnu-emacs "Pascal J. Bourguignon" <pjb@informatimago.com> writes: > Eric Abrahamsen <eric@ericabrahamsen.net> 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. > The problem is that you want your body to access those variables. So > the body must know their names. But you are computing new names that > are uninterned, and therefore unaccessible. Therefore there's no way to > access those temporary variables, from the body. Only code generated by > your macro could access those variables (since the macro has their name > stored in its head .. tags variables. Wow, thanks so much to both of you, this is an excellent lesson in using macros. Richard, the walk-through was much appreciated -- things like that always start out seeming obvious, but then by step three or so I'm perplexed. pp-macroexpand-* will be helpful in the future. So I think I've got it. The behavior that everyone's trying to avoid by using make-symbol is in fact the precise behavior I want: leaking symbols from the macro into the body code. I'm going to hold my brain perfectly still until that sinks in. I had suspected that I'd have to do something along the lines of Pascal's solution -- explicitly providing the args to be bound on each pass -- but I'm inclined to go with Richard's version, since the whole point of this function is to be a *scratch*-buffer way of doing one-off things with Org headings, and I'd like it to be as easy to write as possible. Thanks again! Eric ^ permalink raw reply [flat|nested] 8+ messages in thread
[parent not found: <mailman.9302.1411299483.1147.help-gnu-emacs@gnu.org>]
* Re: macro temp variables [not found] ` <mailman.9302.1411299483.1147.help-gnu-emacs@gnu.org> @ 2014-09-21 20:59 ` Pascal J. Bourguignon 0 siblings, 0 replies; 8+ messages in thread From: Pascal J. Bourguignon @ 2014-09-21 20:59 UTC (permalink / raw) To: help-gnu-emacs Eric Abrahamsen <eric@ericabrahamsen.net> writes: > "Pascal J. Bourguignon" <pjb@informatimago.com> writes: > >> Eric Abrahamsen <eric@ericabrahamsen.net> 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. > >> The problem is that you want your body to access those variables. So >> the body must know their names. But you are computing new names that >> are uninterned, and therefore unaccessible. Therefore there's no way to >> access those temporary variables, from the body. Only code generated by >> your macro could access those variables (since the macro has their name >> stored in its head .. tags variables. > > Wow, thanks so much to both of you, this is an excellent lesson in using > macros. Richard, the walk-through was much appreciated -- things like > that always start out seeming obvious, but then by step three or so I'm > perplexed. pp-macroexpand-* will be helpful in the future. > > So I think I've got it. The behavior that everyone's trying to avoid by > using make-symbol is in fact the precise behavior I want: leaking > symbols from the macro into the body code. I'm going to hold my brain > perfectly still until that sinks in. > > I had suspected that I'd have to do something along the lines of > Pascal's solution -- explicitly providing the args to be bound on each > pass -- but I'm inclined to go with Richard's version, since the whole > point of this function is to be a *scratch*-buffer way of doing one-off > things with Org headings, and I'd like it to be as easy to write as > possible. Then you would just bind those normal, interned, symbols, and document that your macro does this binding: (defmacro org-iterating-headings (&rest body) "Binds the following lexical variables: head item todo tags body-ars to the corresponding org headings, while iterating headings." (destructuring-bind (head item todo tags body-pars) vars `(call-org-iterating-headings (lambda (head item todo tags body-pars) ,@body)))) Notice the advantage of factoring yout the call-org-iterating-heading function: you can easily change the user interface of your macro after the fact, and even provide both. -- __Pascal Bourguignon__ http://www.informatimago.com/ “The factory of the future will have only two employees, a man and a dog. The man will be there to feed the dog. The dog will be there to keep the man from touching the equipment.” -- Carl Bass CEO Autodesk ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2014-09-21 20:59 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2014-09-19 10:49 macro temp variables Eric Abrahamsen 2014-09-19 12:11 ` Nicolas Richard 2014-09-19 15:48 ` Nicolas Richard [not found] <mailman.9181.1411123560.1147.help-gnu-emacs@gnu.org> 2014-09-19 17:33 ` Pascal J. Bourguignon 2014-09-19 17:45 ` Stefan Monnier [not found] ` <mailman.9214.1411148764.1147.help-gnu-emacs@gnu.org> 2014-09-19 18:51 ` Pascal J. Bourguignon 2014-09-21 4:37 ` Eric Abrahamsen [not found] ` <mailman.9302.1411299483.1147.help-gnu-emacs@gnu.org> 2014-09-21 20:59 ` Pascal J. Bourguignon
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).