From: Steven E. Harris
Subject: Macro composition problem
Date: 
Message-ID: <jk4ll8ckfjq.fsf@W003275.na.alarismed.com>
I'm trying to write a macro that generates one of the two following
forms, depending upon a predicate argument:

(defmacro or-test (consider-result-p)
  ...)

,----[ (or-test nil) ]
| (let ((result (some-calc)))
|   imagine-some-huge-multi-line-form)
`----

,----[ (or-test t) ]
| (let ((result (some-calc)))
|   (or result
|       imagine-some-huge-multi-line-form))
`----

In other words, when the predicate consider-result-p is true, we nest
a big multi-line form as a second branch in an or test. When the
predicate is false, we don't bother with the or test and just plant
the multi-line form in there.

At first, I just wrote the macro to generate a nil branch of the or
test when the predicate is false:

(defmacro or-test (consider-result-p)
  `(let ((result (some-calc)))
     (or ,(if consider-result-p
              'result
            'nil)
        imagine-some-huge-multi-line-form)))

,----[ (or-test nil) ]
| (let ((result (some-calc)))
|   (or nil
|       imagine-some-huge-multi-line-form))
`----

That made the macro clear to write, but I'm bothered by that empty or
test in the false case. Maybe "(or nil ...)" just gets optimized away,
but maybe not. The same problem could arise with something less
transparent than "or".

Trying to get rid of it, I came up with this variant:

(defmacro or-test (consider-result-p)
  (let ((body 'imagine-some-huge-multi-line-form))
    `(let ((result (some-calc)))
       ,(if consider-result-p
            `(or result ,body)
          body))))


That produces the desired expansion, but the code is now inside-out,
with the inner body captured first at the top and injected in
later. The actual function is, of course, much larger, and the "body"
form refers to variables bound before the "consider-result-p" test
(say, "result" in the example). It seems to be in poor taste.

I can see that the problem involves tree structure in the source. In
one case, we just want a certain subtree, while in the other case we
want to hang that same subtree under a parent node and preceding
sibling.

Is there a better way write the macro to get the desired expansion?

-- 
Steven E. Harris

From: Peter Seibel
Subject: Re: Macro composition problem
Date: 
Message-ID: <m3r7i4q1he.fsf@gigamonkeys.com>
"Steven E. Harris" <···@panix.com> writes:

> I'm trying to write a macro that generates one of the two following
> forms, depending upon a predicate argument:

Do you really mean to be using the value of consider-result-p at
macro-expansion time? That's a bit odd. (Folks might think that
because they can write: (or-test nil) that they can also write
(or-test *some-predicate*) and will then wonder why the latter behaves
like (or-test t) even when *some-predicate* is NIL.

-Peter

-- 
Peter Seibel                                     ·····@gigamonkeys.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Steven E. Harris
Subject: Re: Macro composition problem
Date: 
Message-ID: <jk4fyykke6g.fsf@W003275.na.alarismed.com>
Peter Seibel <·····@gigamonkeys.com> writes:

> Do you really mean to be using the value of consider-result-p at
> macro-expansion time? That's a bit odd.

Funny you ask. Yes, originally this parameter was meant to be
considered later at execution time. My function was full of lots of
small branches based on this predicate. I factored some of these out
into local lambdas that were then just funcalled at the right place,
trying to pull the redundant testing and branching out of the loops.

Then, thinking about it again, I realized that this predicate will
usually be known "statically", and that I didn't want to accommodate a
run-time decision on which forms to produce. I went from having a
function like this (drastically simplifying):

  (defun make-parser (latchingp) ...)

to this:

  (defmacro gen-parser (latchingp) ...)

  (defun make-parser ()
    (gen-parser nil))

  (defun make-latching-parser ()
    (gen-parser t))


I'm forcing my callers to know at macro-expansion time which kind of
parser they'd like to create so that I can free the parser code from
repetitively trying to figure out whether to exhibit latching behavior
or not.

> (Folks might think that because they can write: (or-test nil) that
> they can also write (or-test *some-predicate*) and will then wonder
> why the latter behaves like (or-test t) even when *some-predicate*
> is NIL.

Indeed, I thought that too when I left my keyboard last night, and had
concluded by this morning that it doesn't work like that. Believe it
or not, this is one case where having a C++ mindset helped: noting the
clear distinction between variables with values known at
"preprocessing-time" or "compile-time" versus those available at
"run-time", even if the same "times" don't quite map to CL.

Given that my intention is not wholly insane, is there a nicer way to
solve the problem?

-- 
Steven E. Harris
From: Peter Seibel
Subject: Re: Macro composition problem
Date: 
Message-ID: <m3k6nwq03s.fsf@gigamonkeys.com>
"Steven E. Harris" <···@panix.com> writes:

> Given that my intention is not wholly insane, is there a nicer way to
> solve the problem?

I don't see anything wrong with the last solution from your original
post.

-Peter

-- 
Peter Seibel                                     ·····@gigamonkeys.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Pascal Costanza
Subject: Re: Macro composition problem
Date: 
Message-ID: <3agld9F68t9d4U1@individual.net>
Steven E. Harris wrote:

> Trying to get rid of it, I came up with this variant:
> 
> (defmacro or-test (consider-result-p)
>   (let ((body 'imagine-some-huge-multi-line-form))
>     `(let ((result (some-calc)))
>        ,(if consider-result-p
>             `(or result ,body)
>           body))))
> 
> 
> That produces the desired expansion, but the code is now inside-out,
> with the inner body captured first at the top and injected in
> later. The actual function is, of course, much larger, and the "body"
> form refers to variables bound before the "consider-result-p" test
> (say, "result" in the example). It seems to be in poor taste.
[...]

> Is there a better way write the macro to get the desired expansion?

I don't see a real problem in your code, but what about this:

(defmacro or-test (consider-result-p)
   `(let ((result (some-calc)))
      ,((lambda (body)
          (if consider-result-p
              `(or result ,body)
             ,body))
        'imagine-some-huge-multi-line-form)))


Pascal
From: Barry Margolin
Subject: Re: Macro composition problem
Date: 
Message-ID: <barmar-8D50E8.21510524032005@comcast.dca.giganews.com>
In article <···············@W003275.na.alarismed.com>,
 "Steven E. Harris" <···@panix.com> wrote:

> Trying to get rid of it, I came up with this variant:
> 
> (defmacro or-test (consider-result-p)
>   (let ((body 'imagine-some-huge-multi-line-form))
>     `(let ((result (some-calc)))
>        ,(if consider-result-p
>             `(or result ,body)
>           body))))

(defmacro or-test (consider-result-p)
  `(let ((result (some-calc)))
     (or ,@(when consider-result-p
                 '(result))
         imagine-some-huge-multi-line-form)))

(or-test nil)

will then expand into

(let ((result (some-calc)))
  (or imagine-some-huge-multi-line-form))

Any implementation that doesn't optimize away a one-argument OR is not 
really worth thinking about.  In fact, I think any realistic 
implementation should also optimize (or nil ...), the form you were 
originally concerned about.

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
From: Sam Steingold
Subject: Re: Macro composition problem
Date: 
Message-ID: <m3oed8juo9.fsf@loiso.podval.org>
> * Steven E. Harris <···@cnavk.pbz> [2005-03-24 11:30:17 -0800]:
>
> I'm bothered by that empty or test in the false case. Maybe "(or nil
> ...)" just gets optimized away, but maybe not.

it _is_ optimized away.  don't worry.
if you must, compare

(disassemble (lambda () (or nil t)))

and

(disassemble (lambda () t))

and see if your implementation produces identical disassemblies.

-- 
Sam Steingold (http://www.podval.org/~sds) running FC3 GNU/Linux
<http://www.openvotingconsortium.org/> <http://www.mideasttruth.com/>
<http://www.memri.org/> <http://pmw.org.il/> <http://www.dhimmi.com/>
MS: our tomorrow's software will run on your tomorrow's HW at today's speed.