From: Jacek Generowicz
Subject: Synonyms
Date: 
Message-ID: <g03d7mydni.fsf@scumbag.ecs.soton.ac.uk>
Given

(defun foo (a b &optional c)
  "Do something interesting"
  (do-something-interesting))

Is there anything to recommend one of the following styles over the
others ?

(defun bar (a b &optional c)
  "A synonym for foo"
  (if c
      (foo a b c)
    (foo a b)))


(defun bar (&rest args)
  "A synonym for foo"
  (apply #'foo args))


(setf (symbol-function 'bar) (symbol-function 'foo))

Thanks,

Jacek

From: Christopher J. Vogt
Subject: Re: Synonyms
Date: 
Message-ID: <3B5D7F23.A4A8C08E@computer.org>
Jacek Generowicz wrote:
> 
> Given
> 
> (defun foo (a b &optional c)
>   "Do something interesting"
>   (do-something-interesting))
> 
> Is there anything to recommend one of the following styles over the
> others ?
> 
> (defun bar (a b &optional c)
>   "A synonym for foo"
>   (if c
>       (foo a b c)
>     (foo a b)))
> 
> (defun bar (&rest args)
>   "A synonym for foo"
>   (apply #'foo args))
> 
> (setf (symbol-function 'bar) (symbol-function 'foo))

Is "none of the above" an available answer?

Since foo is defined to take an optional arg, as is bar:
(defun bar (a b &optional c)
   "A synonym for foo"
   (foo a b c))
Now it really *looks* like the synonym it is.
From: Kent M Pitman
Subject: Re: Synonyms
Date: 
Message-ID: <sfwbsma1l67.fsf@world.std.com>
Jacek Generowicz <···@ecs.soton.ac.uk> writes:

> Given
> 
> (defun foo (a b &optional c)
>   "Do something interesting"
>   (do-something-interesting))
> 
> Is there anything to recommend one of the following styles over the
> others ?

Good question.

> 
[a]
> (defun bar (a b &optional c)
>   "A synonym for foo"
>   (if c
>       (foo a b c)
>     (foo a b)))
> 
[b] 
> (defun bar (&rest args)
>   "A synonym for foo"
>   (apply #'foo args))
> 
[c] 
> (setf (symbol-function 'bar) (symbol-function 'foo))

Well, the [a] is implemented wrong.  It will call (foo a b) when given
(bar a b nil).  If that's acceptable to FOO because [as you show it]
you have an arglist of (a b &optional c), then you might as well implement
BAR in this scenario as:

 (defun bar (a b &optional c)
   "A synonym for foo"
   (foo a b c))

since FOO can't tell the difference between (foo a b nil) and (foo a b).
If FOO might be able to tell the difference, then proper style dictates
that so must BAR, so you want:

 (defun bar (a b &optional (c nil c-p))
   "A synonym for foo"
   (if c-p
       (foo a b c)
     (foo a b)))

in scenario [a].

For scenario [b], I recommend adding
  (defun bar (&rest args)
    "A synonym for foo"
    (declare (dynamic-extent args))
    (apply #'foo args))
so that it doesn't compile code to cons ARGS in the heap (which will be
immediately discarded) if it is capable of not doing so.  There is a minor
semantic quibble as to whether adding this declaration is problematic for
  (apply #'bar '(a b))
given that the language permits (a b) to be shared with the caller and 
that means you are technically incorrectly declaring a heap-consed item to
be otherwise-inaccessible at the end of the call.  However, I've never been
able to find something a compiler would do incorrectly with the heap-consed
item as a result of it, and it's certainly better than having the compiler
compile heap-consed args.  So I do the declaration.  There's no real way for
the programmer to totally win here--I think the language boxes you into a
corner.

Using scenario [a] rather than [b] avoids this problem with the semantics of 
dynamic-extent.

Using scenario [a] is potentially slower since it's got to decode optionals,
test numbers of arguments, etc.  Scenario [b] is probably (but check your
vendor) going to be recognized idiomatically and better optimized.

Using scenario [a] will give you better behavior with control-shift-a
in the editor to see an instant view of the arglist than will [b].

[b] has a definitional advantage that as you maintain the source of FOO,
perhaps changing its arglist, you don't have to worry that the source of BAR
needs to change also.

So I'd bet usually [a] is more user-friendly and [b] is probably faster
and less error-prone to maintain.

For [c], this works only for global functions and can't be used locally
for FLET'd/LABELS'd functions.  Usually that's where people want it so it
doesn't matter a lot.  But once in a while, you can't use scenario [c] so 
I figured I'd mention it.

Also, [c] leaves you an "update" problem if you later patch FOO or BAR.
You have to be sure to do both.

As with [b] above, [c] does have the same a definitional advantage that as 
you maintain the source of FOO, you don't have to also maintain BAR.  This
makes both [b] and [c] less error-prone than [a].

[c] will almost always have a speed advantage over [a] or [b].

Both [a] and [b] have the advantage that (unless tail call elimination is 
done, which it probably isn't in DEBUG=3 optimization levels in most 
implementations) you get some hint in the debugger of whether FOO or BAR
was what was called; in all cases, FOO should be on the stack but in some
cases BAR will also be, which may be helpful in debugging certain things.
Certainly in getting at least a wrong-number-of-args error, [a] will give you
a better diagnostic if you get the args to BAR at other than 2-3, since it
will be BAR, not FOO, that detects the problem and that may narrow your search
for the culprit.

All of [a], [b], and [c] may lose compiler optimizers and inline declarations
for FOO if you've defined them.  Be careful about that.

Btw, the narrow nature of your question to a relatively simple arglist
also avoided issues of whether specials and initargs and so on were involved
that would make the arglists "not trivially cascadable".  I'll omit 
discussion of that to another time, but I wanted to mention it.  I have
a vague recollection that there are some interesting "gotchas" here.

Conclusion: There is no one right answer.  Just a balance of issues as
 appropriate to your application.
From: Pekka P. Pirinen
Subject: Re: Synonyms
Date: 
Message-ID: <uvgk5usy8.fsf@globalgraphics.com>
Kent M Pitman <······@world.std.com> writes:
[excellent stuff, there's just one bit I disagree with]
>   (defun bar (&rest args)
>     "A synonym for foo"
>     (declare (dynamic-extent args))
>     (apply #'foo args))
> so that it doesn't compile code to cons ARGS in the heap (which will be
> immediately discarded) if it is capable of not doing so.  There is a minor
> semantic quibble as to whether adding this declaration is problematic for
>   (apply #'bar '(a b))
> given that the language permits (a b) to be shared with the caller and 
> that means you are technically incorrectly declaring a heap-consed item to
> be otherwise-inaccessible at the end of the call.

The way I read the spec of DYNAMIC-EXTENT, only things that are
otherwise inaccessible at the beginning need to be so at the end.
Make note of the bit that says "for each object xijk that is an
otherwise inaccessible part of vij at any time when vij becomes the
value of vari".  And in fact, the examples in the standard - while not
normative - include the stack-allocated rest list.
-- 
Pekka P. Pirinen, Global Graphics Software, Cambridge, UK
"If you don't look after knowledge, it goes away."
  - Terry Pratchett, The Carpet People
From: Duane Rettig
Subject: Re: Synonyms
Date: 
Message-ID: <44rrpi3n0.fsf@beta.franz.com>
···············@globalgraphics.com (Pekka P. Pirinen) writes:

> Kent M Pitman <······@world.std.com> writes:
> [excellent stuff, there's just one bit I disagree with]
> >   (defun bar (&rest args)
> >     "A synonym for foo"
> >     (declare (dynamic-extent args))
> >     (apply #'foo args))
> > so that it doesn't compile code to cons ARGS in the heap (which will be
> > immediately discarded) if it is capable of not doing so.  There is a minor
> > semantic quibble as to whether adding this declaration is problematic for
> >   (apply #'bar '(a b))
> > given that the language permits (a b) to be shared with the caller and 
> > that means you are technically incorrectly declaring a heap-consed item to
> > be otherwise-inaccessible at the end of the call.
> 
> The way I read the spec of DYNAMIC-EXTENT, only things that are
> otherwise inaccessible at the beginning need to be so at the end.
> Make note of the bit that says "for each object xijk that is an
> otherwise inaccessible part of vij at any time when vij becomes the
> value of vari".  And in fact, the examples in the standard - while not
> normative - include the stack-allocated rest list.

Hmm, I had also agreed with Kent's analysis, but your focus on this
function brought out another aspect that I had missed the first
time around...

Given

[b1]

(defun bar (&rest args)
  "A synonym for foo"
  (apply #'foo args))

and [b2]

(defun bar (&rest args)
  "A synonym for foo"
  (declare (dynamic-extent args))
  (apply #'foo args))

 it turns out that in Allegro CL [b1] is more efficient than [b2].
In Allegro CL, when a &rest arg is not used (either set or referenced)
in any location in the function but the last arg to any number of apply
forms, (and if the variable is not declared dynamic-extent), the
compiler recognizes this and changes the construction.  We call this
an "applyn" situation.  The construction causes the &rest arg to be
ignored, and no restification of the incoming arguments is done at
all, even on the stack.  Then, with the apply call changed to an
"applyn" primitive call with appropriate supporting data, applyn
looks up the stack to the parent function and copies the arguments
down directly from their source, instead of pulling them from a list
which had only been constructed for that purpose...

The reason, then, that [b1] is more efficient than [b2] is that the
&rest list is never constructed in the first case, and there is in
effect one less copying operation involved.  It's pretty amazing to
see how often this construction is actually encountered, especially
in CLOS implementation.

-- 
Duane Rettig          Franz Inc.            http://www.franz.com/ (www)
1995 University Ave Suite 275  Berkeley, CA 94704
Phone: (510) 548-3600; FAX: (510) 548-8253   ·····@Franz.COM (internet)
From: Kent M Pitman
Subject: Re: Synonyms
Date: 
Message-ID: <sfwitg5f6w5.fsf@world.std.com>
Duane Rettig <·····@franz.com> writes:

> [b1]
> 
> (defun bar (&rest args)
>   "A synonym for foo"
>   (apply #'foo args))
> 
> and [b2]
> 
> (defun bar (&rest args)
>   "A synonym for foo"
>   (declare (dynamic-extent args))
>   (apply #'foo args))
> 
>  it turns out that in Allegro CL [b1] is more efficient than [b2].
> In Allegro CL, when a &rest arg is not used (either set or referenced)
> in any location in the function but the last arg to any number of apply
> forms, (and if the variable is not declared dynamic-extent), the
> compiler recognizes this and changes the construction.  We call this
> an "applyn" situation.  The construction causes the &rest arg to be
> ignored, and no restification of the incoming arguments is done at
> all, even on the stack.  Then, with the apply call changed to an
> "applyn" primitive call with appropriate supporting data, applyn
> looks up the stack to the parent function and copies the arguments
> down directly from their source, instead of pulling them from a list
> which had only been constructed for that purpose...
> 
> The reason, then, that [b1] is more efficient than [b2] is that the
> &rest list is never constructed in the first case, and there is in
> effect one less copying operation involved.  It's pretty amazing to
> see how often this construction is actually encountered, especially
> in CLOS implementation.

I hope now that you've noticed it you'll try not to penalize someone
for making this declaration.  I do it all over the place. 
From: Duane Rettig
Subject: Re: Synonyms
Date: 
Message-ID: <4ae1gdg35.fsf@beta.franz.com>
Kent M Pitman <······@world.std.com> writes:

> Duane Rettig <·····@franz.com> writes:
> 
> > [b1]
> > 
> > (defun bar (&rest args)
> >   "A synonym for foo"
> >   (apply #'foo args))
> > 
> > and [b2]
> > 
> > (defun bar (&rest args)
> >   "A synonym for foo"
> >   (declare (dynamic-extent args))
> >   (apply #'foo args))
> > 
> >  it turns out that in Allegro CL [b1] is more efficient than [b2].
> > In Allegro CL, when a &rest arg is not used (either set or referenced)
> > in any location in the function but the last arg to any number of apply
> > forms, (and if the variable is not declared dynamic-extent), the
> > compiler recognizes this and changes the construction.  We call this
> > an "applyn" situation.  The construction causes the &rest arg to be
> > ignored, and no restification of the incoming arguments is done at
> > all, even on the stack.  Then, with the apply call changed to an
> > "applyn" primitive call with appropriate supporting data, applyn
> > looks up the stack to the parent function and copies the arguments
> > down directly from their source, instead of pulling them from a list
> > which had only been constructed for that purpose...
> > 
> > The reason, then, that [b1] is more efficient than [b2] is that the
> > &rest list is never constructed in the first case, and there is in
> > effect one less copying operation involved.  It's pretty amazing to
> > see how often this construction is actually encountered, especially
> > in CLOS implementation.
> 
> I hope now that you've noticed it you'll try not to penalize someone
> for making this declaration.  I do it all over the place. 

Well, you're right.

I must admit, my first inclination was to respond sarcastically (as in
"Of course you aren't penalized - you still get the same fast
stack-consing code that you always got before applyn was implemented").
However, without even arguing, you've changed my mind.  I had been
equating the dynamic-extent declaration with stack-consing, and thinking
that when users declare dynamic-extent, it is because they wanted
stack-consing so that's what we needed to do.  But that is not at all
what dynamic-extent means.  And in reality, the applyn optimization does
indeed satisfy the dynamic-extent declaration, so there is no conflict,
even though it in fact does not cons at all (not even on the stack...)

I'll be considering such a change, where the applyn optimization takes
precedence over dynamic-extent declaration instead of the other way
around.

-- 
Duane Rettig          Franz Inc.            http://www.franz.com/ (www)
1995 University Ave Suite 275  Berkeley, CA 94704
Phone: (510) 548-3600; FAX: (510) 548-8253   ·····@Franz.COM (internet)
From: Reini Urban
Subject: stack-consing (was: Synonyms)
Date: 
Message-ID: <9ki13u$hht$1@fstgss02.tu-graz.ac.at>
Duane Rettig <·····@franz.com> wrote:
: I must admit, my first inclination was to respond sarcastically (as in
: "Of course you aren't penalized - you still get the same fast
: stack-consing code that you always got before applyn was implemented").
: However, without even arguing, you've changed my mind.  I had been
: equating the dynamic-extent declaration with stack-consing, and thinking
: that when users declare dynamic-extent, it is because they wanted
: stack-consing so that's what we needed to do.  But that is not at all
: what dynamic-extent means.  And in reality, the applyn optimization does
: indeed satisfy the dynamic-extent declaration, so there is no conflict,
: even though it in fact does not cons at all (not even on the stack...)

"stack-consing" in contrast to "heap-consing" seems to be a misnomer to me.
Are we allowed to say "stack-consing" at all? It looks so expensive this
way.

In this context "consing" is reduced to "allocating memory" only, but we
know that consing does more than just allocating memory. In contrast to
"stack allocation" where it comes for free.
-- 
Reini Urban
http://xarch.tu-graz.ac.at/acadwiki/AutoLispFaq
From: Duane Rettig
Subject: Re: stack-consing (was: Synonyms)
Date: 
Message-ID: <4d76blyzi.fsf@beta.franz.com>
Reini Urban <······@x-ray.at> writes:

> Duane Rettig <·····@franz.com> wrote:
> : I must admit, my first inclination was to respond sarcastically (as in
> : "Of course you aren't penalized - you still get the same fast
> : stack-consing code that you always got before applyn was implemented").
> : However, without even arguing, you've changed my mind.  I had been
> : equating the dynamic-extent declaration with stack-consing, and thinking
> : that when users declare dynamic-extent, it is because they wanted
> : stack-consing so that's what we needed to do.  But that is not at all
> : what dynamic-extent means.  And in reality, the applyn optimization does
> : indeed satisfy the dynamic-extent declaration, so there is no conflict,
> : even though it in fact does not cons at all (not even on the stack...)
> 
> "stack-consing" in contrast to "heap-consing" seems to be a misnomer to me.
> Are we allowed to say "stack-consing" at all? It looks so expensive this
> way.

Well, depending on your point of view, it is expensive.  Not as expensive
as heap consing, of course, but expensive nonetheless.

The least expensive way to use memory is to not copy it at all.

> In this context "consing" is reduced to "allocating memory" only, but we
> know that consing does more than just allocating memory. In contrast to
> "stack allocation" where it comes for free.

Precisely.  Allocation on the stack is essentially free, if you have enough
of it, because usually the stack pointer is being decremented once per
function call anyway, so you can just change the decrement to get more
allocation (N.B. &rest args are slightly different, because the compiler
doesn't know how many args are coming in, so the stack decrement must be
calculated at runtime in addition to the static space being allocated).
But heap allocation isn't much more expensive; it can come down to just one
extra pointer increment and a fullness test.

But you can't really use any such allocated space unless you initialize
it.  This is why I used the term "stack-consing in the first place.
In a &rest list, you have to link all the cdrs of the stack locations to
chain them up, and you have to also move the LispVals that are in the
argument locations to the cars of the list.  So it's certainly not free.
At least not as free as leaving the arguments where they were at the
time of the call.

-- 
Duane Rettig          Franz Inc.            http://www.franz.com/ (www)
1995 University Ave Suite 275  Berkeley, CA 94704
Phone: (510) 548-3600; FAX: (510) 548-8253   ·····@Franz.COM (internet)
From: Kent M Pitman
Subject: Re: Synonyms
Date: 
Message-ID: <sfwlml1f7g3.fsf@world.std.com>
···············@globalgraphics.com (Pekka P. Pirinen) writes:

> 
> Kent M Pitman <······@world.std.com> writes:
> [excellent stuff, there's just one bit I disagree with]
> >   (defun bar (&rest args)
> >     "A synonym for foo"
> >     (declare (dynamic-extent args))
> >     (apply #'foo args))
> > so that it doesn't compile code to cons ARGS in the heap (which will be
> > immediately discarded) if it is capable of not doing so.  There is a minor
> > semantic quibble as to whether adding this declaration is problematic for
> >   (apply #'bar '(a b))
> > given that the language permits (a b) to be shared with the caller and 
> > that means you are technically incorrectly declaring a heap-consed item to
> > be otherwise-inaccessible at the end of the call.
> 
> The way I read the spec of DYNAMIC-EXTENT, only things that are
> otherwise inaccessible at the beginning need to be so at the end.
> Make note of the bit that says "for each object xijk that is an
> otherwise inaccessible part of vij at any time when vij becomes the
> value of vari".  And in fact, the examples in the standard - while not
> normative - include the stack-allocated rest list.

Well, your interpretation certainly confirms my own selfish desires on this
issue, but something still makes me nervous about it, so call my remarks above
a "conflict of interest disclosure".  Then again, it's been a while since
I read through all of the talk of dynanmic extent and OIP's in tremendous
detail either, so maybe my memory of the issue to worry about is faded somehow.

In practice, I think the real question is whether there's some implementation
that compiles the above wrong.  Hopefully not.