From: bradb
Subject: Equivalent of C's "global/static" variables
Date: 
Message-ID: <1137438894.590258.217140@g44g2000cwa.googlegroups.com>
I've not been doing Lisp for very long, and have a few questions about
global/static variables.
I think that I understand (defvar) variables, they are global and
special (dynamically scoped?).  Reassigning them with LET is a
stack-like effect where they go back to their original value after the
let expires.
Lets say I have a simple application that draws a line 100 pixels long,
from a start point toward the mouse.  If the mouse is more than 100
pixels away, the line moves toward the mouse.  So when you move the
mouse the line mostly follows it.
For arguments sake, lets say I want to do this with 2 functions, UPDATE
and DRAW, which recalculates the line properties & draws it,
respectively.  I will need some data that is shared between these two
functions to store the line points, lets say p1 and p2.

In a simple C app, I would probably just make these global variables.
I can do this with Lisp and DEFVAR, but the variables don't really need
to be special.

In C++ I might make a class, and pass that to the functions.  I can do
this with CLOS, and long term it might be a good idea, but is probably
overkill here.

I am currently, in Lisp, surrounding both functions with a let:
(let ((p1 nil) (p2 nil))
(defun update (x y) ...)
(defun draw ()...))

The functions are simple and clean, the data is somewhat encapsulated,
though if I need to share the data widely later it might be a problem.
So, my question is - what is the Lispy way to do what I want to do?
Are there downsides to encapsulating multiple functions with a LET?
This all doesn't feel very functional either though (I am also new to
functional programming), is this an easy problem to solve in an FP way?

Cheers
Brad

From: jayessay
Subject: Re: Equivalent of C's "global/static" variables
Date: 
Message-ID: <m3r778c5ol.fsf@rigel.goldenthreadtech.com>
"bradb" <··············@gmail.com> writes:

> (let ((p1 nil) (p2 nil))
> (defun update (x y) ...)
> (defun draw ()...))

Using closures like this is a reasonable way to go for what you want
here.


> The functions are simple and clean, the data is somewhat encapsulated,

The data is pretty much totally encapsulated - nothing but the two
functions can access it.


> though if I need to share the data widely later it might be a problem.

Just add some accessors or provide a more full API for the purpose.


> So, my question is - what is the Lispy way to do what I want to do?

What you have seems reasonable.


> Are there downsides to encapsulating multiple functions with a LET?

Nothing general - your _specific_ requirements may imply some
downside, but nothing you've said so far would indicate any.


> This all doesn't feel very functional either though (I am also new to

Don't worry about that, it doesn't matter. (Don't start thinking
ideologically here - keep focused on what makes sense for the specific
needs!)


/Jon

-- 
'j' - a n t h o n y at romeo/charley/november com
From: Kenny Tilton
Subject: Re: Equivalent of C's "global/static" variables
Date: 
Message-ID: <RfVyf.3339$SD.3243@news-wrt-01.rdc-nyc.rr.com>
bradb wrote:
> I've not been doing Lisp for very long, and have a few questions about
> global/static variables.

..snip..

> 
> I am currently, in Lisp, surrounding both functions with a let:
> (let ((p1 nil) (p2 nil))
> (defun update (x y) ...)
> (defun draw ()...))
> 
> The functions are simple and clean, the data is somewhat encapsulated,
> though if I need to share the data widely later it might be a problem.
> So, my question is - what is the Lispy way to do what I want to do?
> Are there downsides to encapsulating multiple functions with a LET?
> This all doesn't feel very functional either though (I am also new to
> functional programming), is this an easy problem to solve in an FP way?

1. Seems very functional to me, but I am no FP expert.

2. Lispniks pride their language on its being multi-paradigm, so FP or 
not FP does not keep us awake at night. Otoh, FP /does/ have all the 
advantages of code quality that crowd yammers about. So see #1. :)

kenny
From: Brian Downing
Subject: Re: Equivalent of C's "global/static" variables
Date: 
Message-ID: <mO6zf.738308$xm3.667145@attbi_s21>
In article <························@g44g2000cwa.googlegroups.com>,
bradb <··············@gmail.com> wrote:
> I am currently, in Lisp, surrounding both functions with a let:
> (let ((p1 nil) (p2 nil))
> (defun update (x y) ...)
> (defun draw ()...))

> Are there downsides to encapsulating multiple functions with a LET?

Most implementations will not let you compile UPDATE and DRAW if the
LET was not itself compiled.  This is a bit of a pain from a REPL.

[from prompt.franz.com:]
CL-USER(5): (let ((p1 nil) (p2 nil))
              (defun update (x y) (setf p1 x p2 y))
              (defun draw () (list p1 p2)))
DRAW
CL-USER(6): (update 1 2)
2
CL-USER(7): (draw)      
(1 2)
CL-USER(8): (compile 'update)
; While compiling UPDATE:
Error: This declare form is not in a place declares are allowed:
(DECLARE (OPTIMIZE (SPEED 3) (SAFETY 0)))
Problem detected when processing
       (DECLARE (OPTIMIZE (SPEED 3) (SAFETY 0)))
inside (SETF P1 X)
inside (SETF P1 X ...)
inside (BLOCK UPDATE (SETF P1 X ...))
inside (PROGN (BLOCK UPDATE (SETF P1 X ...)))
  [condition type: PARSE-ERROR]

Not a terribly helpful error on Allegro's part.  CMUCL at least says
"#<Interpreted Function UPDATE {480245C1}> was defined in a non-null
environment."

If you're using something like Slime it's trivial though - just hit 
C-c C-c while in the LET top-level form in your editor buffer.

-bcd
-- 
*** Brian Downing <bdowning at lavos dot net> 
From: bradb
Subject: Re: Equivalent of C's "global/static" variables
Date: 
Message-ID: <1137515779.698856.164550@z14g2000cwz.googlegroups.com>
Thanks for the feedback guys.  Good to know that I am not doing
anything to weird :)

Cheers
Brad
From: Duane Rettig
Subject: Re: Equivalent of C's "global/static" variables
Date: 
Message-ID: <o07j8yohqs.fsf@franz.com>
Brian Downing <·············@lavos.net> writes:

> In article <························@g44g2000cwa.googlegroups.com>,
> bradb <··············@gmail.com> wrote:
>> I am currently, in Lisp, surrounding both functions with a let:
>> (let ((p1 nil) (p2 nil))
>> (defun update (x y) ...)
>> (defun draw ()...))
>
>> Are there downsides to encapsulating multiple functions with a LET?
>
> Most implementations will not let you compile UPDATE and DRAW if the
> LET was not itself compiled.

Yes, this is because the standard specifies undefined behavior for
some non-null lexical environments, especially the kind which a
LET form usually creates.

>  This is a bit of a pain from a REPL.

We agree, and that is why Allegro CL attempts to extend the
undefined behavior by defining behavior for it - unfortunately
that behavior is incomplete:

> [from prompt.franz.com:]
> CL-USER(5): (let ((p1 nil) (p2 nil))
>               (defun update (x y) (setf p1 x p2 y))
>               (defun draw () (list p1 p2)))
> DRAW
> CL-USER(6): (update 1 2)
> 2
> CL-USER(7): (draw)      
> (1 2)
> CL-USER(8): (compile 'update)
> ; While compiling UPDATE:
> Error: This declare form is not in a place declares are allowed:
> (DECLARE (OPTIMIZE (SPEED 3) (SAFETY 0)))
> Problem detected when processing
>        (DECLARE (OPTIMIZE (SPEED 3) (SAFETY 0)))
> inside (SETF P1 X)
> inside (SETF P1 X ...)
> inside (BLOCK UPDATE (SETF P1 X ...))
> inside (PROGN (BLOCK UPDATE (SETF P1 X ...)))
>   [condition type: PARSE-ERROR]
>
> Not a terribly helpful error on Allegro's part.

Agreed.  This is due to a bug in Allegro CL's part, during an attempt
to provide this extension to the standard in an area where the standard
specifies undefined behavior.  See below [*] for further explanation
about our extension.

>  CMUCL at least says
> "#<Interpreted Function UPDATE {480245C1}> was defined in a non-null
> environment."

This message, however, suggests a bug in CMUCL - whereas is perfectly
reasonable to decide not to compile the lexical closure for update in
the particular non-null lexical environment it was given, the spec
is more careful about how it states where the undefined behavior starts
and ends; what it actually says, in teh descriptuon for COMPILE, is:

   "The consequences are undefined if the lexical environment
    surrounding the function to be compiled contains any bindings
    other than those for macros, symbol macros, or declarations."

So in fact, something like:

(macrolet ((foo (x) `(car ,x)))
  (defun bar (y) (foo y)))

...

(compile 'bar)

should in fact work.  Perhaps they've fixed it now, but the versions
of cmucl I try out from time to time don't handle this; apparently
they refuse to compile any interpreted function within a non-null
lexical environment (which is not what the spec allows).  Perhaps
there should have been a glossary term such as "trivial lexical
environment" to cover a lexical environment which is non-null, but
has no lexical definitons other than macros, symbol-macros, or
declarations - then an error message like "... was defined in a
non-trivial lexical environment." would have been an easy and well-defined
error message...

> If you're using something like Slime it's trivial though - just hit 
> C-c C-c while in the LET top-level form in your editor buffer.

The reason for this is because it is not COMPILE that is being used,
but rather COMPILE-FILE.


[*] Compilation of definitions within the original form clearly has
undefined consequences, so this part of the discussion deals with
parts over and above the spec.  I agree that the error message that
Allegro CL gives is not helpful.  However, if there had not been a
bug in the way Allegro CL extends the spec to try to make some sense
out of calling compile on these definitions, then you would not have
gotten any error at all, but would have instead gotten a compiled
functon which would have done the intuitively correct thing.  We're
able to consider doing this because of the similarity between the
representations of compiled and interpreted environments in our
implementation.

It is unfortunate that you selected 'update to compile, since that
one demonstrates the bug.  The bug is that we rewrite the accesses
to each variable as locally forms, and they do not expand well
in setf situations.  I will be working on fixing that, probably for
future releases, but fortunately I can show you how it _does_ work
in situations where setf is not involved, and in fact the code
is already provided (we just compile the other function!):

CL-USER(1): (let ((p1 nil) (p2 nil))
              (defun update (x y) (setf p1 x p2 y))
              (defun draw () (list p1 p2)))
DRAW
CL-USER(2): (update 1 2)
2
CL-USER(3): (draw)
(1 2)
CL-USER(4): (compile 'draw)
DRAW
NIL
NIL
CL-USER(5): (draw)
(1 2)
CL-USER(6): (update 3 4)
4
CL-USER(7): (draw)
(3 4)
CL-USER(8): (describe #'update)
#<Interpreted Closure UPDATE> is a NEW FUNCTION.
  The arguments are (X Y)
CL-USER(9): (describe #'draw)
#<Function DRAW> is a NEW COMPILED-FUNCTION.
  The arguments are NIL
CL-USER(10): 


-- 
Duane Rettig    ·····@franz.com    Franz Inc.  http://www.franz.com/
555 12th St., Suite 1450               http://www.555citycenter.com/
Oakland, Ca. 94607        Phone: (510) 452-2000; Fax: (510) 452-0182   
From: Brian Downing
Subject: Re: Equivalent of C's "global/static" variables
Date: 
Message-ID: <QMazf.496199$084.337014@attbi_s22>
Duane,

Very informative, thanks!  A few points:

In article <··············@franz.com>, Duane Rettig  <·····@franz.com> wrote:
> >  CMUCL at least says
> > "#<Interpreted Function UPDATE {480245C1}> was defined in a non-null
> > environment."
> 
> This message, however, suggests a bug in CMUCL - whereas is perfectly
> reasonable to decide not to compile the lexical closure for update in
> the particular non-null lexical environment it was given, the spec
> is more careful about how it states where the undefined behavior starts
> and ends; what it actually says, in teh descriptuon for COMPILE, is:
> 
>    "The consequences are undefined if the lexical environment
>     surrounding the function to be compiled contains any bindings
>     other than those for macros, symbol macros, or declarations."
> 
> So in fact, something like:
> 
> (macrolet ((foo (x) `(car ,x)))
>   (defun bar (y) (foo y)))
> 
> ...
> 
> (compile 'bar)
> 
> should in fact work.  Perhaps they've fixed it now, but the versions
> of cmucl I try out from time to time don't handle this; apparently
> they refuse to compile any interpreted function within a non-null
> lexical environment (which is not what the spec allows).  Perhaps
> there should have been a glossary term such as "trivial lexical
> environment" to cover a lexical environment which is non-null, but
> has no lexical definitons other than macros, symbol-macros, or
> declarations - then an error message like "... was defined in a
> non-trivial lexical environment." would have been an easy and well-defined
> error message...

Agreed, and I think I got this right in SBCL's evaluator (due to be
finished and integrated into mainline well before the year 2038):

* (macrolet ((foo (x) `(car ,x)))
    (defun bar (y) (foo y)))
BAR
* (compile 'bar)
BAR
NIL
NIL

* (let ((x 4))
    (defun set-x (v) (setf x v))
    (defun get-x () x))
GET-X
* (compile 'get-x)
debugger invoked on a SB-EVAL::ENVIRONMENT-TOO-COMPLEX-ERROR in thread 313:
  Lexical environment of #<INTERPRETED-FUNCTION GET-X> is too complex to
  compile.

> [*] Compilation of definitions within the original form clearly has
> undefined consequences, so this part of the discussion deals with
> parts over and above the spec.  I agree that the error message that
> Allegro CL gives is not helpful.  However, if there had not been a
> bug in the way Allegro CL extends the spec to try to make some sense
> out of calling compile on these definitions, then you would not have
> gotten any error at all, but would have instead gotten a compiled
> functon which would have done the intuitively correct thing.  We're
> able to consider doing this because of the similarity between the
> representations of compiled and interpreted environments in our
> implementation.

> It is unfortunate that you selected 'update to compile, since that
> one demonstrates the bug.  The bug is that we rewrite the accesses
> to each variable as locally forms, and they do not expand well
> in setf situations.  I will be working on fixing that, probably for
> future releases, but fortunately I can show you how it _does_ work
> in situations where setf is not involved, and in fact the code
> is already provided (we just compile the other function!):

I must say I'm very impressed you can make this work even some of the
time!  Thinking about how to make this work in the above SBCL evaluator
generally gave me a headache, and after seeing every other implementation
I tried not do it either (and reading in the spec that it's undefined),
I decided to punt.  It's doubly unfortunate that in all the cases I've
tried it on ACL I've stumbled across this bug.

-bcd
-- 
*** Brian Downing <bdowning at lavos dot net> 
From: Duane Rettig
Subject: Re: Equivalent of C's "global/static" variables
Date: 
Message-ID: <o0r776ejoh.fsf@franz.com>
Brian Downing <·············@lavos.net> writes:

> In article <··············@franz.com>, Duane Rettig  <·····@franz.com> wrote:
>> 
>> So in fact, something like:
>> 
>> (macrolet ((foo (x) `(car ,x)))
>>   (defun bar (y) (foo y)))
>> 
>> ...
>> 
>> (compile 'bar)
>> 
>> should in fact work.  Perhaps they've fixed it now, but the versions
>> of cmucl I try out from time to time don't handle this; apparently
>> they refuse to compile any interpreted function within a non-null
>> lexical environment (which is not what the spec allows).  Perhaps
>> there should have been a glossary term such as "trivial lexical
>> environment" to cover a lexical environment which is non-null, but
>> has no lexical definitons other than macros, symbol-macros, or
>> declarations - then an error message like "... was defined in a
>> non-trivial lexical environment." would have been an easy and well-defined
>> error message...
>
> Agreed, and I think I got this right in SBCL's evaluator (due to be
> finished and integrated into mainline well before the year 2038):
>
> * (macrolet ((foo (x) `(car ,x)))
>     (defun bar (y) (foo y)))
> BAR
> * (compile 'bar)
> BAR
> NIL
> NIL

Yes, I had tried this on sbcl and it seemed to have worked.

> * (let ((x 4))
>     (defun set-x (v) (setf x v))
>     (defun get-x () x))
> GET-X
> * (compile 'get-x)
> debugger invoked on a SB-EVAL::ENVIRONMENT-TOO-COMPLEX-ERROR in thread 313:
>   Lexical environment of #<INTERPRETED-FUNCTION GET-X> is too complex to
>   compile.

Yes, this is a better error message, in my opinion, because it directs
the user toward the realization that there might be a non-null environment
which will allow such compilations...

>> [*] Compilation of definitions within the original form clearly has
>> undefined consequences, so this part of the discussion deals with
>> parts over and above the spec.  I agree that the error message that
>> Allegro CL gives is not helpful.  However, if there had not been a
>> bug in the way Allegro CL extends the spec to try to make some sense
>> out of calling compile on these definitions, then you would not have
>> gotten any error at all, but would have instead gotten a compiled
>> functon which would have done the intuitively correct thing.  We're
>> able to consider doing this because of the similarity between the
>> representations of compiled and interpreted environments in our
>> implementation.
>
>> It is unfortunate that you selected 'update to compile, since that
>> one demonstrates the bug.  The bug is that we rewrite the accesses
>> to each variable as locally forms, and they do not expand well
>> in setf situations.  I will be working on fixing that, probably for
>> future releases, but fortunately I can show you how it _does_ work
>> in situations where setf is not involved, and in fact the code
>> is already provided (we just compile the other function!):
>
> I must say I'm very impressed you can make this work even some of the
> time!

Thanks.

>  Thinking about how to make this work in the above SBCL evaluator
> generally gave me a headache, and after seeing every other implementation

<shameless-plug>

Well, there is an open-source implementation of Environments Access which
will allow you to define interpreter semantics that allow such cross-
pollination...  see http://www.lispwire.com/entry-proganal-envaccess-des
If you are an sbcl implementor, you can always grab the code and implement
the evaluator, even if you don't attach it to the compiler.  The evaluator
was the first thing I implemented in this module, explicitly decided
because the original CLtL2 descriptions were intended for a compiler, and
I wanted to be sure it would be acceptably fast as a substrate for an
evaluator.

</shameless-plug>

> I tried not do it either (and reading in the spec that it's undefined),
> I decided to punt.  It's doubly unfortunate that in all the cases I've
> tried it on ACL I've stumbled across this bug.

Yes, and it is fortunate that you showed this one on this ng.  Most of
the uses we or our customers have seen (at least those customers who have
requested the enhancement above and beyond the spec), when facing this kind
of situation where there is a closed-over value and two accessors: a
reader and a writer, have tended to compile the reader, because it tends
to be the one used more often - so I had not seen this bug until this
morning.

-- 
Duane Rettig    ·····@franz.com    Franz Inc.  http://www.franz.com/
555 12th St., Suite 1450               http://www.555citycenter.com/
Oakland, Ca. 94607        Phone: (510) 452-2000; Fax: (510) 452-0182   
From: Christophe Rhodes
Subject: Re: Equivalent of C's "global/static" variables
Date: 
Message-ID: <sqr776e69j.fsf@cam.ac.uk>
Duane Rettig <·····@franz.com> writes:

> Brian Downing <·············@lavos.net> writes:
>> * (macrolet ((foo (x) `(car ,x)))
>>     (defun bar (y) (foo y)))
>> BAR
>> * (compile 'bar)
>> BAR
>> NIL
>> NIL
>
> Yes, I had tried this on sbcl and it seemed to have worked.

Unless you're doing rather more source patching than I expect (but do
correct me if I'm wrong!) your experiment won't have measured the
ability of sbcl's compiler to compile uncompiled functions.  Brian's
transcript refers to his branch containing an interpreter, which is
not part of the mainline code; binaries and code of sbcl as
distributed as packages have a compile-always strategy, and so the
call to COMPILE is a no-op.

Christophe
From: Duane Rettig
Subject: Re: Equivalent of C's "global/static" variables
Date: 
Message-ID: <o0u0c2uyby.fsf@franz.com>
Christophe Rhodes <·····@cam.ac.uk> writes:

> Duane Rettig <·····@franz.com> writes:
>
>> Brian Downing <·············@lavos.net> writes:
>>> * (macrolet ((foo (x) `(car ,x)))
>>>     (defun bar (y) (foo y)))
>>> BAR
>>> * (compile 'bar)
>>> BAR
>>> NIL
>>> NIL
>>
>> Yes, I had tried this on sbcl and it seemed to have worked.
>
> Unless you're doing rather more source patching than I expect (but do
> correct me if I'm wrong!) your experiment won't have measured the
> ability of sbcl's compiler to compile uncompiled functions.  Brian's
> transcript refers to his branch containing an interpreter, which is
> not part of the mainline code; binaries and code of sbcl as
> distributed as packages have a compile-always strategy, and so the
> call to COMPILE is a no-op.

Ah, I see; the functions were already compiled!

Thanks.

-- 
Duane Rettig    ·····@franz.com    Franz Inc.  http://www.franz.com/
555 12th St., Suite 1450               http://www.555citycenter.com/
Oakland, Ca. 94607        Phone: (510) 452-2000; Fax: (510) 452-0182   
From: Rob Warnock
Subject: Re: Equivalent of C's "global/static" variables
Date: 
Message-ID: <z-adnUcxXvR7clDenZ2dnUVZ_sCdnZ2d@speakeasy.net>
Duane Rettig  <·····@franz.com> wrote:
+---------------
| Brian Downing <·············@lavos.net> writes:
| > bradb <··············@gmail.com> wrote:
| >> I am currently, in Lisp, surrounding both functions with a let:
| >> (let ((p1 nil) (p2 nil))
| >> (defun update (x y) ...)
| >> (defun draw ()...))
| >
| >> Are there downsides to encapsulating multiple functions with a LET?
| >
| > Most implementations will not let you compile UPDATE and DRAW if the
| > LET was not itself compiled.
| 
| Yes, this is because the standard specifies undefined behavior for
| some non-null lexical environments, especially the kind which a
| LET form usually creates.
| 
| >  This is a bit of a pain from a REPL.
+---------------

But it *can* be done, even with CMUCL, albeit indirectly (see below).

+---------------
| >  CMUCL at least says
| > "#<Interpreted Function UPDATE {480245C1}> was defined in a non-null
| > environment."
| 
| This message, however, suggests a bug in CMUCL - [not allowing MACROLET]
...
| ...should in fact work.  Perhaps they've fixed it now, but the versions
| of cmucl I try out from time to time don't handle this; apparently
| they refuse to compile any interpreted function within a non-null
| lexical environment (which is not what the spec allows).
+---------------

As was suggested in another branch, if the LET itself were compiled
then everything should "just work". So perhaps we CMUCL REPL users
should add the following macro to our toolboxes:  ;-}  ;-}

    cmu> (defmacro compile* (&body body)
	   `(funcall (compile nil (lambda () ,@body))))

    COMPILE*
    cmu> (compile*
	   (let ((p1 nil) (p2 nil))
	     (defun update (x y) (setf p1 x p2 y))
	     (defun draw () (list p1 p2))))
    ; Converted UPDATE.
    ; Converted DRAW.
    ; Compiling LAMBDA NIL: 
    ; Compiling Top-Level Form: 

    DRAW
    cmu> (describe *)

    DRAW is an internal symbol in the COMMON-LISP-USER package.
    Function: #<Closure Over Function DRAW {58A06521}>
    Function arguments:
      There are no arguments.
    Its defined argument types are:
      NIL
    Its result type is:
      LIST
    On Tuesday, 1/17/06 11:22:34 pm PST it was compiled from:
    #(#'(LAMBDA # #))
    Its closure environment is:
    0: #<Value Cell NIL {58A0650F}>
    1: #<Value Cell NIL {58A06507}>
    cmu> (draw)

    (NIL NIL)
    cmu> (update 11 22)

    22
    cmu> (draw)

    (11 22)
    cmu> (describe 'draw)

    DRAW is an internal symbol in the COMMON-LISP-USER package.
    Function: #<Closure Over Function DRAW {58A06521}>
    Function arguments:
      There are no arguments.
    Its defined argument types are:
      NIL
    Its result type is:
      LIST
    On Tuesday, 1/17/06 11:22:34 pm PST it was compiled from:
    #(#'(LAMBDA # #))
    Its closure environment is:
    0: #<Value Cell 22 {58A0650F}>
    1: #<Value Cell 11 {58A06507}>
    cmu> 


-Rob

-----
Rob Warnock			<····@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607
From: jayessay
Subject: Re: Equivalent of C's "global/static" variables
Date: 
Message-ID: <m3irshcx8h.fsf@rigel.goldenthreadtech.com>
····@rpw3.org (Rob Warnock) writes:

> As was suggested in another branch, if the LET itself were compiled
> then everything should "just work". So perhaps we CMUCL REPL users
> should add the following macro to our toolboxes:  ;-}  ;-}
> 
>     cmu> (defmacro compile* (&body body)
> 	   `(funcall (compile nil (lambda () ,@body))))

Right.  Slime, Ilisp (and probably most/all other IDEs) provide that
same "idiom" for you automatically via C-cC-c in the form.  Most of
this discussion really is more "academic" than of practical interest.


/Jon

-- 
'j' - a n t h o n y at romeo/charley/november com