From: Chris Capel
Subject: Please review macro
Date: 
Message-ID: <10bhh6m4gdgfsd2@corp.supernews.com>
Not a big task (line numbers added for commentary):

1 (defmacro collect (var form &key (append-elements t))
  "\"Appends\" value of FORM to value of VAR and sets VAR to resulting
value. If FORM is an atom, it's added to the list. If it's a list, its
elements are added unless keyword APPEND-ELEMENTS is nil, where the list
itself is added. The value of VAR must be of type (or LIST NIL)."
2   (let ((····@ (gensym)))
3     `(let ((,····@ ,form))
4       (setq ,var (append ,var
5                   ,(if append-elements
6                        `(if (listp ,····@)
7                             ,····@
8                             (list ,····@))
9                        `(list ,····@)))))))

My main question is if there's some function, or simple combination of
functions, in CL that does this. (I guess it could be argued that this
macro's a pretty simple combination.) And no, I don't want to use LOOP. I
guess I really don't want to learn LOOP. That's probably why I wrote this
macro.

If not, is this good macro-writing style? It's my first macro. I like using
an ·@' to denote my gensym. Is that bad? Is the docstring OK?

And how about the macro itself? I get the feeling there's a better name for
"append-elements" I couldn't think of. Of course, it's meant to be used in
a loop, but this gives you a feeling for what it does:

> (let ((·@ nil))
  (collect ·@ 3)
  (collect ·@ (list 4 5))
  (collect ·@ (list 6 7) :append-elements nil)
  (collect ·@ 8 :append-elements nil))

(3 4 5 (6 7) 8)

Would it be more useful to have collect return atoms within their own
sublist when append-elements is nil? The output in that case would change
to

(3 4 5 (6 7) (8))

Chris Capel

From: Svein Ove Aas
Subject: Re: Please review macro
Date: 
Message-ID: <c9aods$2n3b$1@news.dataguard.no>
Chris Capel wrote:

> Not a big task (line numbers added for commentary):
> 
> 1 (defmacro collect (var form &key (append-elements t))
>   "\"Appends\" value of FORM to value of VAR and sets VAR to resulting
> value. If FORM is an atom, it's added to the list. If it's a list, its
> elements are added unless keyword APPEND-ELEMENTS is nil, where the list
> itself is added. The value of VAR must be of type (or LIST NIL)."
> 2   (let ((····@ (gensym)))
> 3     `(let ((,····@ ,form))
> 4       (setq ,var (append ,var
> 5                   ,(if append-elements
> 6                        `(if (listp ,····@)
> 7                             ,····@
> 8                             (list ,····@))
> 9                        `(list ,····@)))))))
> 
> My main question is if there's some function, or simple combination of
> functions, in CL that does this. (I guess it could be argued that this
> macro's a pretty simple combination.) And no, I don't want to use LOOP.
> I guess I really don't want to learn LOOP. That's probably why I wrote
> this macro.

You *should* learn loop; it helps a lot, much of the time. I don't see how
it would help here, though... I'm just a newbie, though.

> If not, is this good macro-writing style? It's my first macro. I like
> using an ·@' to denote my gensym. Is that bad? Is the docstring OK?
> 
You're welcome to use any symbol you like; there are some conventions at
http://www.cliki.net/Naming conventions, but they don't seem to specify
this.

Gensymmed symbols shouldn't usually make it to outside code, so it doesn't
really matter.


There *is* one fairly huge problem, though:
You're evaluating var multiple times; this is a bad idea, though I have a
hard time figuring out how it will cause trouble when you're using setq
on it... wiser heads should know.

I guess you're aware of that, since you do copy form.

I'm not entirely sure what's up with append-elements in the if. Shouldn't
you have a , before it?
From: Chris Capel
Subject: Re: Please review macro
Date: 
Message-ID: <10bhpv032a9o8a0@corp.supernews.com>
Svein Ove Aas wrote:

> You *should* learn loop; it helps a lot, much of the time. I don't see how
> it would help here, though... I'm just a newbie, though.

Yes, someday. But I do think that when you don't need a complicated macro
like loop except that you want to "collect" something you shouldn't /have/
to use loop. Then again, maybe what I want is remove-if. Hmm.

> There *is* one fairly huge problem, though:
> You're evaluating var multiple times; this is a bad idea, though I have a
> hard time figuring out how it will cause trouble when you're using setq
> on it... wiser heads should know.
>
> I guess you're aware of that, since you do copy form.

This macro only works when VAR is a symbol. I figured there will be no
reasonable circumstances where collect would be used with a form that
evaluates to a symbol, as opposed to a literal symbol. Then again, now that
I think about it more carefully, I suppose even the former could be the
case, in a /very/ abstract procedure. That would be unusual, though. Can
anyone think of an example of this?

Regardless, to be technically correct, I /should/ gensym var as well.

> I'm not entirely sure what's up with append-elements in the if. Shouldn't
> you have a , before it?

No, because line 5 begins with a comma. At the point where append-elements
appears, there's no backquote to escape from.

Chris Capel
From: Kenny Tilton
Subject: Re: Please review macro
Date: 
Message-ID: <O16uc.101764$Nn4.21416557@twister.nyc.rr.com>
Chris Capel wrote:

> Svein Ove Aas wrote:
> 
> 
>>You *should* learn loop; it helps a lot, much of the time. I don't see how
>>it would help here, though... I'm just a newbie, though.
> 
> 
> Yes, someday. But I do think that when you don't need a complicated macro
> like loop except that you want to "collect" something you shouldn't /have/
> to use loop. Then again, maybe what I want is remove-if. Hmm.
> 
> 
>>There *is* one fairly huge problem, though:
>>You're evaluating var multiple times; this is a bad idea, though I have a
>>hard time figuring out how it will cause trouble when you're using setq
>>on it... wiser heads should know.
>>
>>I guess you're aware of that, since you do copy form.
> 
> 
> This macro only works when VAR is a symbol. I figured there will be no
> reasonable circumstances where collect would be used with a form that
> evaluates to a symbol, as opposed to a literal symbol. Then again, now that
> I think about it more carefully, I suppose even the former could be the
> case, in a /very/ abstract procedure. That would be unusual, though. Can
> anyone think of an example of this?
> 
> Regardless, to be technically correct, I /should/ gensym var as well.

You have me thinking. Can that even work? Are you going to evaluate the 
var arg at runtime?:

     (let ((···@ (gensym)))
        `(let ((,···@ ,var))
            (setq ,···@... nah, that won't work. That would expand into 
a setq of the gensym, not the value bound to the gensym (the var the 
user is trying to modify.) You would need to apply setq, but it is a 
special form, no can do.

I think part of the interface for a macro includes whether an argument 
is evaluated, and that is that.

But i'm kinda winging it here and am braced for a wave of corrections.

:)

kenny

-- 
Home? http://tilton-technology.com
Cells? http://www.common-lisp.net/project/cells/
Cello? http://www.common-lisp.net/project/cello/
Why Lisp? http://alu.cliki.net/RtL%20Highlight%20Film
Your Project Here! http://alu.cliki.net/Industry%20Application
From: Kenny Tilton
Subject: Re: Please review macro
Date: 
Message-ID: <EN5uc.101763$Nn4.21414400@twister.nyc.rr.com>
Svein Ove Aas wrote:
> Chris Capel wrote:
> 
> 
>>Not a big task (line numbers added for commentary):
>>
>>1 (defmacro collect (var form &key (append-elements t))
>>  "\"Appends\" value of FORM to value of VAR and sets VAR to resulting
>>value. If FORM is an atom, it's added to the list. If it's a list, its
>>elements are added unless keyword APPEND-ELEMENTS is nil, where the list
>>itself is added. The value of VAR must be of type (or LIST NIL)."
>>2   (let ((····@ (gensym)))
>>3     `(let ((,····@ ,form))
>>4       (setq ,var (append ,var
>>5                   ,(if append-elements
>>6                        `(if (listp ,····@)
>>7                             ,····@
>>8                             (list ,····@))
>>9                        `(list ,····@)))))))
>>
>>My main question is if there's some function, or simple combination of
>>functions, in CL that does this. (I guess it could be argued that this
>>macro's a pretty simple combination.) And no, I don't want to use LOOP.
>>I guess I really don't want to learn LOOP. That's probably why I wrote
>>this macro.
> 
> 
> You *should* learn loop; it helps a lot, much of the time. I don't see how
> it would help here, though... I'm just a newbie, though.

Word. Took me eight years before I learned the error of my ways.

> 
> 
>>If not, is this good macro-writing style? It's my first macro. I like
>>using an ·@' to denote my gensym. Is that bad? Is the docstring OK?
>>
> 
> You're welcome to use any symbol you like; there are some conventions at
> http://www.cliki.net/Naming conventions, but they don't seem to specify
> this.
> 
> Gensymmed symbols shouldn't usually make it to outside code, so it doesn't
> really matter.
> 
> 
> There *is* one fairly huge problem, though:
> You're evaluating var multiple times; this is a bad idea, though I have a
> hard time figuring out how it will cause trouble when you're using setq
> on it... wiser heads should know.

It's OK. Macros evaluate some forms, others not. A var like this would 
normally be specified explicitly.

> 
> I guess you're aware of that, since you do copy form.
> 
> I'm not entirely sure what's up with append-elements in the if. Shouldn't
> you have a , before it?

Not the way he is using it, which is to make interesting decisions at 
compile time about into what code the particular macro invocation should 
expand. More concretely, the comma at the start of ,(if 
append-elements... ) holds for the whole form following it, so things in 
there are compile-time code with direct access to the macro args.

But you might have meant that the macro would be more useful if 
append-elements were evaluated at run time. If so, I agree. Otherwise we 
get this:

CELLS(7): (defmacro t-f (t-or-f) (if t-or-f `'true `'false))
T-F
CELLS(8): (t-f t)
TRUE
CELLS(9): (t-f nil)
FALSE
CELLS(10): (t-f (not t))
TRUE <uh-oh>

kenny


-- 
Home? http://tilton-technology.com
Cells? http://www.common-lisp.net/project/cells/
Cello? http://www.common-lisp.net/project/cello/
Why Lisp? http://alu.cliki.net/RtL%20Highlight%20Film
Your Project Here! http://alu.cliki.net/Industry%20Application
From: Svein Ove Aas
Subject: Re: Please review macro
Date: 
Message-ID: <c9aq55$2nps$1@news.dataguard.no>
Kenny Tilton wrote:

>> I'm not entirely sure what's up with append-elements in the if.
>> Shouldn't you have a , before it?
> 
> Not the way he is using it, which is to make interesting decisions at
> compile time about into what code the particular macro invocation should
> expand. More concretely, the comma at the start of ,(if
> append-elements... ) holds for the whole form following it, so things in
> there are compile-time code with direct access to the macro args.
> 
No, you're quite right. I didn't notice the , before (if, and that does
make a lot more sense.

> But you might have meant that the macro would be more useful if
> append-elements were evaluated at run time. If so, I agree. Otherwise we
> get this:
> 
> CELLS(7): (defmacro t-f (t-or-f) (if t-or-f `'true `'false))
> T-F
> CELLS(8): (t-f t)
> TRUE
> CELLS(9): (t-f nil)
> FALSE
> CELLS(10): (t-f (not t))
> TRUE <uh-oh>
> 
Eh.
I'll go read On Lisp again.
From: Kenny Tilton
Subject: Re: Please review macro
Date: 
Message-ID: <D86uc.101765$Nn4.21417408@twister.nyc.rr.com>
Svein Ove Aas wrote:

> Kenny Tilton wrote:
> 
> 
>>>I'm not entirely sure what's up with append-elements in the if.
>>>Shouldn't you have a , before it?
>>
>>Not the way he is using it, which is to make interesting decisions at
>>compile time about into what code the particular macro invocation should
>>expand. More concretely, the comma at the start of ,(if
>>append-elements... ) holds for the whole form following it, so things in
>>there are compile-time code with direct access to the macro args.
>>
> 
> No, you're quite right. I didn't notice the , before (if, and that does
> make a lot more sense.
> 
> 
>>But you might have meant that the macro would be more useful if
>>append-elements were evaluated at run time. If so, I agree. Otherwise we
>>get this:
>>
>>CELLS(7): (defmacro t-f (t-or-f) (if t-or-f `'true `'false))
>>T-F
>>CELLS(8): (t-f t)
>>TRUE
>>CELLS(9): (t-f nil)
>>FALSE
>>CELLS(10): (t-f (not t))
>>TRUE <uh-oh>
>>
> Eh.
> I'll go read On Lisp again.

:) Better yet, add (print t-or-f) to the above macro and (when (listp 
t-or-f) (print (car t-or-f))), and try different calls such as:

    (t-f 'hi-mom)

Also, macroexpand a form and see your print statements run, evaluate 
them and see them not run. Mind you, at the repl I guess you get both 
compile-time and then right away run-time, so it may not produce such 
dramatic results unless you compile the macro first.

This is also how one debugs macros, and soon enough one internalizes 
compile vs run time and can toss off macros without much thought, 
switching back and forth between the compiletime code being written and 
runtime code one has in mind.

kenny


-- 
Home? http://tilton-technology.com
Cells? http://www.common-lisp.net/project/cells/
Cello? http://www.common-lisp.net/project/cello/
Why Lisp? http://alu.cliki.net/RtL%20Highlight%20Film
Your Project Here! http://alu.cliki.net/Industry%20Application
From: Thomas A. Russ
Subject: Re: Please review macro
Date: 
Message-ID: <ymipt8jqlbd.fsf@sevak.isi.edu>
Svein Ove Aas <··············@brage.info> writes:

> 
> Chris Capel wrote:
> 
> > Not a big task (line numbers added for commentary):
> > 
> > 1 (defmacro collect (var form &key (append-elements t))
> >   "\"Appends\" value of FORM to value of VAR and sets VAR to resulting
> > value. If FORM is an atom, it's added to the list. If it's a list, its
> > elements are added unless keyword APPEND-ELEMENTS is nil, where the list
> > itself is added. The value of VAR must be of type (or LIST NIL)."
> > 2   (let ((····@ (gensym)))
> > 3     `(let ((,····@ ,form))
> > 4       (setq ,var (append ,var
> > 5                   ,(if append-elements
> > 6                        `(if (listp ,····@)
> > 7                             ,····@
> > 8                             (list ,····@))
> > 9                        `(list ,····@)))))))
> > 
> > My main question is if there's some function, or simple combination of
> > functions, in CL that does this. (I guess it could be argued that this
> > macro's a pretty simple combination.) And no, I don't want to use LOOP.
> > I guess I really don't want to learn LOOP. That's probably why I wrote
> > this macro.
> 
> You *should* learn loop; it helps a lot, much of the time. I don't see how
> it would help here, though... I'm just a newbie, though.

Actually, I think the loop solution is sufficiently concise that it
doesn't need any additional wrappers.  Here is what it would look like:

(setq var (loop for element in form
                when (and append-elements (listp element))
                append element
                else collect element))


-- 
Thomas A. Russ,  USC/Information Sciences Institute
From: Kalle Olavi Niemitalo
Subject: Re: Please review macro
Date: 
Message-ID: <87u0xzf1r3.fsf@Astalo.kon.iki.fi>
Chris Capel <·····@ibanktech.net> writes:

>   "\"Appends\" value of FORM to value of VAR and sets VAR to resulting
> value. If FORM is an atom, it's added to the list. If it's a list, its
> elements are added unless keyword APPEND-ELEMENTS is nil, where the list
> itself is added. The value of VAR must be of type (or LIST NIL)."

I think the documentation string should indicate that
APPEND-ELEMENTS is checked at macroexpand time, rather than
evaluated as a form by the expansion.

Because NIL is a specifier for the empty type, (or list nil)
means the same as just LIST.  Perhaps you intended (or list null)
instead, but LIST already means (or cons null), so you should
just use that.

> 2   (let ((····@ (gensym)))
> 3     `(let ((,····@ ,form))

I don't like this use of @ when there are commas nearby.
It looks too much like the ,@ syntax.

> 4       (setq ,var (append ,var

Perhaps the macro could allow the VAR to be a generalized reference.

Calling APPEND in a loop can easily lead to quadratic complexity.

> My main question is if there's some function, or simple combination of
> functions, in CL that does this.

I don't think there is any simpler way to write this macro.
There may be ways to simplify specific uses of it, though.
From: Chris Capel
Subject: Re: Please review macro
Date: 
Message-ID: <10bhstqev0ulmb2@corp.supernews.com>
[Kalle Olavi Niemitalo wrote many helpful suggestions]

I'll take those into consideration, thanks.

> Perhaps the macro could allow the VAR to be a generalized reference.

You mean allowing VAR to be any setf-able form? How would this be
implemented?

> Calling APPEND in a loop can easily lead to quadratic complexity.

Could it? All that's being consed in any one call to collect would be its
form argument again. VAR's value is being reused.

Chris Capel
From: Barry Margolin
Subject: Re: Please review macro
Date: 
Message-ID: <barmar-E69869.00290130052004@comcast.dca.giganews.com>
In article <···············@corp.supernews.com>,
 Chris Capel <·····@ibanktech.net> wrote:

> [Kalle Olavi Niemitalo wrote many helpful suggestions]
> 
> I'll take those into consideration, thanks.
> 
> > Perhaps the macro could allow the VAR to be a generalized reference.
> 
> You mean allowing VAR to be any setf-able form? How would this be
> implemented?

Much the same way as it's done in PUSH.  Consider that your macro isn't 
very different from PUSH; PUSH uses CONS to produce the new value, while 
your macro uses APPEND.

To do it properly you need to use DEFINE-SETF-EXPANDER.

> > Calling APPEND in a loop can easily lead to quadratic complexity.
> 
> Could it? All that's being consed in any one call to collect would be its
> form argument again. VAR's value is being reused.

I think he referring to calling COLLECT in a loop.  Each invocation 
calls APPEnD, which is O(n), where n is the current length of the list.

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
From: Thomas A. Russ
Subject: Re: Please review macro
Date: 
Message-ID: <ymir7szqlgy.fsf@sevak.isi.edu>
Chris Capel <·····@ibanktech.net> writes:

> 
> [Kalle Olavi Niemitalo wrote many helpful suggestions]
> 
> I'll take those into consideration, thanks.
> 
> > Perhaps the macro could allow the VAR to be a generalized reference.
> 
> You mean allowing VAR to be any setf-able form? How would this be
> implemented?
> 
> > Calling APPEND in a loop can easily lead to quadratic complexity.
> 
> Could it? All that's being consed in any one call to collect would be its
> form argument again. VAR's value is being reused.

Sure.  Because your analysis of the consing is incorrect.  Append itself
needs to cons its entire first(*) argument into a fresh list in order to
do add the new elements to the end non-destructively.  In other words,
when you call
  (append '(a b c) '(d))
append must COPY (a b c) and then link the final element into the end of
the resulting copy.  This is a source of hidden copying.

Ways to get around this:

(1) Simple solution.  Use NCONC instead of APPEND.  This eliminates the
consing, since NCONC destructively modifies the list.  It doesn't fix
all of the performance problems, though, since NCONC still needs to move
down the list in order to find the end to modify.  This still gives you
quadratic performance, since you repeatedly move down the list, but at
least you don't get quadratic consing.

(2) Better still simple solution.  Build the list in reverse order and
then at the end call NREVERSE on the list.  This eliminates quadratic
behavior and quadratic consing.  It means you traverse the list twice
rather than once, but it's still an improvement on quadratic
performance.  This is actually a common idiom (or is this a pattern
now?) for doing collections in code.

(3)  Better, but more complicated solution.  Use a separate pointer to
the end of the list and do the splicing yourself.  This gives you the
best performance for adding elements to the end of the list, since it
conses only as much as necessary (once for the entire list) and
traverses the list only once.  It is a lot more complicated to setup the
pointers and make sure that they are properly moved during the buliding
of the list structure.  Because of this complicate, (2) is often the
solution of choice for normal coding.  But the beauty of macros is that
it allows you to hide this complicated machinery, and you only have to
get it right once, in the macro definition.  For a macro, this would be
my preferred choice.  By the way, this is what the LOOP macro does for
its collection operators.

> 
> Chris Capel

(*) For completeness: Append actually must copy its first n-1 arguments
for the case when you specify more arguments than just 2


-- 
Thomas A. Russ,  USC/Information Sciences Institute