[net.ai] Common LISP style standards.

ins_amrh@jhunix.UUCP (Martin R. Hall) (05/08/86)

	It seems that my original request for information on LISP coding 
standards was not very lucid.  Let me clarify.
	We are doing everything in Common LISP, but are looking for
standards in regards to coding *style*.  For contract work, we need
relatively explicit rules for these things.  The standards should
answer these types of questions:

	- How do you keep track of the side effects of destructive functions
	  such as sort, nconc, replaca, mapcan, delete-if, etc?
	- When should you use macros vs. functions?
	- How do you reference global variables?  Usually you enclose it
	  in "*"s, but how do you differentiate between your own vars and
	  Common LISP vars such as *standard-input*, *print-level*, etc?
	- Documentation ideas?
	- When to use DOLIST vs MAPCAR?
	- DO vs LOOP?
	- Indentation/format ideas?  Or do you always write it like the
	  pretty-printer would print it?
	- NULL vs ENDP, FIRST vs CAR, etc.  Some would say "FIRST" is
	  more mnemonic, but does that mean you need to use
	  (first (rest (first X))) instead of (cadar X) ??
	- etc, etc.

	It looks like I will be putting together the standards for our
group here, but it would be nice to see some ideas other people had first.
Anyone have anything?
	Thanks!
				-Marty Hall

Arpa: hall@hopkins			 MP 600
CSNET: hall.hopkins@csnet-relay		 AI and Simulation Dept.
uucp: ..seismo!umcp-cs!jhunix!ins_amrh   Martin Marietta Baltimore Aerospace
      ..allegra!hopkins!hall		 103 Chesapeake Park Plaza
					 Baltimore, MD 21220
					 (301) 682-0917.

shebs@utah-cs.UUCP (Stanley Shebs) (05/10/86)

In article <2784@jhunix.UUCP> ins_amrh@jhunix.UUCP (Martin R. Hall) writes:
>
>	We are doing everything in Common LISP, but are looking for
>standards in regards to coding *style*.

The "correct" style depends on whether your hackers are from CMU, MIT,
Stanford, Utah, University of Southern ND at Hoople, ... :-)
The following remarks are based on 4 years with Franz, PSL, and Common
Lisp, reading as well as writing, but are nevertheless highly prejudiced.

>	- How do you keep track of the side effects of destructive functions
>	  such as sort, nconc, replaca, mapcan, delete-if, etc?

There are very few circumstances when it is appropriate to use
destructive functions.  There are two classes of exceptions:  for efficiency,
or the algorithm depends on it.  In the first case, you only use the
destructive operations on newly consed cells, and NEVER on things passed
in as function arguments.  In the second case, you have lots of discussion
about why the algorithm needs destructive ops and how it uses them
("since our image is 1000x1000, we replace the pixels to avoid consing
another image...").

>	- When should you use macros vs. functions?

Use macros only if you need new syntax, for instance a defining form
that your program uses a lot.  In a game I wrote a while ago, there was
a macro called def-thing which took a bunch of numbers and symbols.
If it had been a function, the "'" key would have been worn out...
Sometimes macros are useful to represent a commonly appearing bit of
code that you don't want to call as a function.  But this usually
loses on space what it gains in speed.

>	- How do you reference global variables?  Usually you enclose it
>	  in "*"s, but how do you differentiate between your own vars and
>	  Common LISP vars such as *standard-input*, *print-level*, etc?

Use "*"s, no differentiation.

>	- Documentation ideas?

File headers are good, especially for programs that wander to different
operating systems.  The commenting style in the Common Lisp book is good.
Documentation strings don't seem like a big win, but they probably make
more sense in very elaborate programming environments.  I always put
doc strings on defvars.

>	- When to use DOLIST vs MAPCAR?

Mapcar returns something, dolist doesn't.  To return a list, mapcar
must cons a lot, and dolist doesn't cons at all.  Consing is bad.  :-)

>	- DO vs LOOP?

Whatever turns you on.

>	- Indentation/format ideas?  Or do you always write it like the
>	  pretty-printer would print it?

I always write like the editor formats it.  This can create problems
if two people are using different editors or different customizations
of the editor.  What you see in the Common Lisp book is a good place
to start for getting your editor to indent properly.

Personally, I find it most readable to have a block of comments in
front of the function, then a blank line, then the function.  I also
prefer to minimize the number of comments scattered about in the
function body.  Frequently the structure of the function tells a lot,
but is obscured by comments inserted randomly.  Consider, too, that
a 1-page function + 2 pages of comments = 3 pages of function, which
is *really* hard to read!

>	- NULL vs ENDP, FIRST vs CAR, etc.  Some would say "FIRST" is
>	  more mnemonic, but does that mean you need to use
>	  (first (rest (first X))) instead of (cadar X) ??

Null vs endp is pretty clearcut, since endp may error out, where null
would just return nil.  No more than 1% of all Lisp programs will
behave predictably if they get a dotted list instead of a normal one,
but nobody seems to care...

On first vs car, everybody has their favorites.  I prefer c...r combos,
but others hate it when I use cadr instead of second.  Fortunately, such
circumstances are rare.  If you feel the urge to put together a data
structure that has more than 2 pieces, use a defstruct.  Your code will
be more readable *and* more efficient (since implementors can put in
all sorts of performance hacks for structures).  If I were a manager,
I would fire anybody who used anything but car, cdr, and cadr (and they
wouldn't be saved by doing (car (car (cdr (cdr X)))) either!)

>	- etc, etc.

Avoid cond if you only have one test, use "if" instead.  Saves two pairs
of parens and a "T"... (i.e. it's easier to read).  Short functions are
better than long ones.  In any competent Lisp implementation, the cost
of a function call is quite low, and shouldn't be considered.
I've only written a handful of functions longer than 20 lines...
Sequence functions and mapping functions are generally preferable to
handwritten loops, since the Lisp wizards will probably have spent
a lot of time making them both efficient and correct (watch out though;
quality varies from implementation to implementation).
More generally, Lisp programs usually benefit from the encouragement
of a "functional programming" style, where functions do few side-effects
that extend beyond the function's body.  Easier to read, easier to debug,
easier to maintain.

Standard dictums of programming practice still apply in Lisp, i.e. always
put in a default case on any mult-way conditional - the constructs ecase
and ccase are useful in this respect.  There are lots of others I don't
remember at the moment... somebody should write a book that concentrates
on Lisp programming instead of laundry listifying 400 functions...

>				-Marty Hall

							stan shebs

bsmith@uiucdcsp.CS.UIUC.EDU (05/14/86)

A couple of short comments.
	First, about comments.  You might want to embed into a function
a string that will print out as on the fly documentation if the system
supports it (Symbolics does).  This helps when using a function you wrote
2 months earlier that's lost somewhere in 200 pages of code.

	Second, there are a couple of rules about using conditionals that
make a lot of sense.  If you have a single condition followed by a single
then statement followed by a single else statement, use "if."  If you have
a single condition followed by a single then statement and no else
statement, use "when."  If you have a single negative condition followed
by a single then statement, use "unless."  If you have multiple conditions,
or need to use progn anywhere, a cond is more readable.

michaelm@bcsaic.UUCP (michael maxwell) (05/15/86)

In article <3787@utah-cs.UUCP> shebs@utah-cs.UUCP (Stanley Shebs) writes:
>Sequence functions and mapping functions are generally preferable to
>handwritten loops, since the Lisp wizards will probably have spent
>a lot of time making them both efficient and correct (watch out though;
>quality varies from implementation to implementation).

I'm in a little different boat, since we're using Franz rather than Common
Lisp, so perhaps the issues are a bit different when you're using Monster, I
mean Common, Lisp... so at the risk of rushing in where angels etc.:

A common situation we find ourselves in is the following.  We have a long list,
and we wish to apply some test to each member of the list.  However, at some
point in the list, if the test returns a certain value, there is no need to
look further: we can jump out of processing the list right there, and thus
save time.  Now you can jump out of a do loop with "(return <value>)", but you
can't jump out of a mapc (mapcar etc.) with "return."  So we wind up using
"do" a lot of places where it would otherwise be natural to use "mapcar".  I
suppose I could use "catch" and "throw", but that looks so much like "goto"
that I feel sinful if I use that solution...

Any style suggestions?
-- 
Mike Maxwell
Boeing Artificial Intelligence Center
	...uw-beaver!uw-june!bcsaic!michaelm

martin@kuling.UUCP (Erik Martin) (05/18/86)

In article <2784@jhunix.UUCP> ins_amrh@jhunix.UUCP writes:
>
>	- How do you keep track of the side effects of destructive functions
>	  such as sort, nconc, replaca, mapcan, delete-if, etc?
Don't use them. I use destruction only when I need circular objects or
when I need to speed up a program. In the latter case I write it strictly
functional first and then substitute 'remove' with 'delete' and so on. This
should not affect the semantics of the program if it is 'correctly' written 
from the beginning. But it's really a task for the compiler so You shouldn't
need to think about it.

>	- When should you use macros vs. functions?
I only use macros when i need a new syntax or a 'unusuall' evaluation
of the arguments. (like FEXPR in Franz and MacLisp.)

>	- How do you reference global variables?  Usually you enclose it
>	  in "*"s, but how do you differentiate between your own vars and
>	  Common LISP vars such as *standard-input*, *print-level*, etc?
Allways "*"s. No differentiation.

>	- Documentation ideas?
An 'owerview' description in the file header, more detailed on top of each
function. Very few comments inline, use long function and variable names
instead. Documentation strings in global variables and top level (user)
functions.

>	- When to use DOLIST vs MAPCAR?
Quite obvious. Use DOLIST when you want to scan through a list, i.e. just 
look at it. At the end of the list it returns NIL or the optional return form.
You can also return something with en explicit RETURN. Use MAPCAR when you
want to build a *new* list with a function applied to each element.

>	- DO vs LOOP?
Write what you mean. If you mean 'repeat until dooms day' (whithout any
variables bound) then use LOOP. 

>	- Indentation/format ideas?  Or do you always write it like the
>	  pretty-printer would print it?
A lot of white space in the code. The rest is very personal and hard to set
up rules for. Nice editors usually have good ideas about how it should look
like.

>	- NULL vs ENDP, FIRST vs CAR, etc.  Some would say "FIRST" is
>	  more mnemonic, but does that mean you need to use
>	  (first (rest (first X))) instead of (cadar X) ??
Again, write what you mean. If you mean 'is this the end of the list
we are just working with?' then use ENDP, if you mean 'is this NIL (an empty
list)?', use NULL, and if you mean 'is this false?' use NOT.
Write FIRST if you mean the first element of a list, SECOND for the second,
THIRD for the third...and compinations of these when appropriate. At some 
limit this gets very messy though, and C*R is better. But in that case you
perhaps should write your own accessor functions. When working with cons'es 
I always use CAR and CDR.


My general rule is : Write what you mean and leave the task of efficiency
to the implementation and compiler.


					Per-Erik Martin
-- 
Per-Erik Martin,  Uppsala University, Sweden
UUCP: martin@kuling.UUCP  (...!{seismo,mcvax}!enea!kuling!martin)

shebs@utah-cs.UUCP (Stanley Shebs) (05/29/86)

In article <545@bcsaic.UUCP> michaelm@bcsaic.UUCP (michael maxwell) writes:

>I'm in a little different boat, since we're using Franz rather than Common
>Lisp

I remember Franz (vaguely)... :-)

>A common situation we find ourselves in is the following.  We have a long list,
>and we wish to apply some test to each member of the list.  However, at some
>point in the list, if the test returns a certain value, there is no need to
>look further: we can jump out of processing the list right there, and thus
>save time.

Common Lisp provides "some", "every", "notany", and "notevery" functions
which all do variations of what you're asking for.  They take a predicate
and one or more sequences as arguments, and apply the predicate to each
element in the sequence, and may stop in the middle.  The behavior is
sufficiently specified for you to use side effects in the predicate.
BTW, if these four functions weren't around, Common Lisp would be smaller.

>I suppose I could use "catch" and "throw", but that looks so much like "goto"
>that I feel sinful if I use that solution...

"Sinfulness" is a silly concept that quite a few folks in the computer
community have gotten into - a sort of aftereffect of structured programming.
The *real* reason for using higher-level constructs is efficiency, both
in programmer and execution time.

>Mike Maxwell

							stan shebs
							utah-cs!shebs

rpk@lmi-angel.UUCP (Bob Krajewski) (05/30/86)

In article <> michaelm@bcsaic.UUCP (michael maxwell) writes:
>In article <3787@utah-cs.UUCP> shebs@utah-cs.UUCP (Stanley Shebs) writes:
>>Sequence functions and mapping functions are generally preferable to
>>handwritten loops, since the Lisp wizards will probably have spent
>>a lot of time making them both efficient and correct (watch out though;
>>quality varies from implementation to implementation).

This is very true.  It will be interesting to see how Lisp compiler
technology meets the challenge...

>A common situation we find ourselves in is the following.  We have a long
>list, 
>and we wish to apply some test to each member of the list.  However, at some
>point in the list, if the test returns a certain value, there is no need to
>look further: we can jump out of processing the list right there, and thus
>save time.  Now you can jump out of a do loop with "(return <value>)", but you
>can't jump out of a mapc (mapcar etc.) with "return."  So we wind up using
>"do" a lot of places where it would otherwise be natural to use "mapcar".  I
>suppose I could use "catch" and "throw", but that looks so much like "goto"
>that I feel sinful if I use that solution...

There are two Common Lispy ways doing this.  The first is the use the
function (SOME predicate sequence &rest more-sequences), which returns the
first non-NIL result of the application of the predicate to the each set of
elements of the sequences (like map).  Since this is a generic sequence
function that can take either vectors or lists, you'll probably want to
write something like

	(some #'(lambda (x)
		  (when (wonderful-p (sibling x)) (father x)))
	      (the list a-list))

A good compiler would do two things here: it would first notice that the
only sequence is a list.  Thus, the ``stepping'' function for the sequence
type (CDR, and CAR for element selection) is known in advance.  And since
that is so, it can open code the loop, thus generating a DO-like thing that
you would have otherwise written by hand.

Another way is to use CATCH and THROW.  When the THROW is lexically visible
from the CATCH, very good code can be generated in certain cases.  As for
whether it's icky or not, at least CATCH establishes a lexical scope for
where the ``goto'' is valid, when the THROW is visible.

-- 
Robert P. Krajewski
Internet/MIT: RPK@MC.LCS.MIT.EDU
        UUCP: ...{cca,harvard,mit-eddie}!lmi-angel!rpk

bzs@bu-cs.UUCP (Barry Shein) (06/01/86)

[re: Franz Lisp]

>Now you can jump out of a do loop with "(return <value>)", but you
>can't jump out of a mapc (mapcar etc.) with "return."  So we wind up using
>"do" a lot of places where it would otherwise be natural to use "mapcar".  I
>suppose I could use "catch" and "throw", but that looks so much like "goto"
>that I feel sinful if I use that solution...
>
>Any style suggestions?
>-- 
>Mike Maxwell
>Boeing Artificial Intelligence Center

Howsabout:


(defun foo (x)
    (prog nil
          (mapc '(lambda (y)
                         (cond ((null y) (return 'DONE)) (t (print y))))
                 
                x)))

try for example (foo '(a b nil c d))

	-Barry Shein, Boston University

dsmith@hplabsc.UUCP (David Smith) (06/02/86)

> In article <3787@utah-cs.UUCP> shebs@utah-cs.UUCP (Stanley Shebs) writes:
> >Sequence functions and mapping functions are generally preferable to
> >handwritten loops, ...
> 
> I'm in a little different boat, since we're using Franz rather than Common
> Lisp, so perhaps the issues are a bit different ...
> A common situation we find ourselves in is the following.  We have a long list,
> and we wish to apply some test to each member of the list.  However, at some
> point in the list, if the test returns a certain value, there is no need to
> look further: we can jump out of processing the list right there, and thus
> save time.  ...
> 
> Mike Maxwell

CMU incorporated functions of CMUlisp into Franz, and these are apparently
shipped with Franz:  at least, on my computer, they are in
/usr/src/ucb/lisp/lisplib/cmufncs.l.  One of these functions is the
function some.

	(some 'mylist 'func1 'func2)

returns the first tail of mylist for which func1 of its car returns a
non-nil value.  Otherwise nil is returned.  Successive tails of mylist
are obtained by repeated application of func2 (usually cdr, or nil,
which implies cdr).  A nice cover macro for this is "exists".
Example:
	(exists i '(2 5 3 8 4 1) (> i 6))
returns (8 4 1).

			David Smith
			HP Labs

king@kestrel.UUCP (06/03/86)

   From: michaelm@bcsaic.UUCP (michael maxwell)
   Newsgroups: net.ai,net.lang.lisp
   Date: 15 May 86 17:42:18 GMT
   Reply-To: michaelm@bcsaic.UUCP (michael maxwell)
   Distribution: net

	.	.	.

   A common situation we find ourselves in is the following.  We have
	a long list, 
   and we wish to apply some test to each member of the list.  However, at some
   point in the list, if the test returns a certain value, there is no need to
   look further: we can jump out of processing the list right there, and thus
   save time.  Now you can jump out of a do loop with "(return
	<value>)", but you 
   can't jump out of a mapc (mapcar etc.) with "return."  So we wind up using
   "do" a lot of places where it would otherwise be natural to use "mapcar".  I
   suppose I could use "catch" and "throw", but that looks so much like "goto"
   that I feel sinful if I use that solution...

   Any style suggestions?

I'm way behind in this group, so I apologize in advance if you have
seen this solution or a better one before.

You might try

(prog () 
   (mapcar #'(lambda (y) (when (you-like y) (return (result-for y))))
           x)))

I tried it, and it works.  It doesn't seem dirty to me, and it should
be efficient.  Even if the return point of a prog is such that it
forces the lexical closure to be non-vacuous, this shouldn't be a
problem when compiled.

   -- 
   Mike Maxwell
   Boeing Artificial Intelligence Center
	   ...uw-beaver!uw-june!bcsaic!michaelm

-dick