all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* 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

* 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

* 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

* 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

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.