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
christer@kuling.UUCP (Christer Johansson) (05/13/86)
In article <2784@jhunix.UUCP> of Sun, 11-May-86 04:58:41 GMT ins_amrh@jhunix.UUCP writes: > - 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? I'd suggest enclosing your global var's in something else then stars. > - 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) ?? I use car, cdr, cons when I use conses as nodes in trees, but first, second, third, rest, n:th etc. when I use them as sequences. I think Steel says something on the ue of endp vs. null vs. not. I think null and endp works different on arguments that are conses with cdr being an other atom then null. -- SMail: Christer Johansson UUCP: {seismo,seismo!mcvax}!enea!kuling!christer Sernandersv. 9:136 ARPA: enea!kuling!christer@SEISMO.CSS.GOV S-752 63 Uppsala Phone: Int. +46 - 18 46 31 54 SWEDEN Nat. 018 - 46 31 54
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