lisp users and v endo rs conference august 10 1993 t uto
play

Lisp Users and V endo rs Conference August 10, 1993 T uto - PDF document

Lisp Users and V endo rs Conference August 10, 1993 T uto rial on Go o d Lisp Programming St yle P eter No rvig Sun Microsystems Labs Inc. Kent Pitman Ha rlequin, Inc. P o rtions cop yright c 1992, 1993


  1. 1.2. Ho w do I kno w if it's go o d? Be Sp eci�c Be as sp eci�c as y our data abstractions w a rrant, but no mo re. Cho ose: ;; more specific ;; more abstract (mapc #'process -wo rd (map nil #'process- wo rd (first sentences) ) (elt sentences 0)) Most sp eci�c conditional: � if fo r t w o-b ranch exp ression � when, unless fo r one-b ranch statement � and, or fo r b o olean value only � cond fo r multi-b ranch statement o r exp ression ;; Violates Expectat ion : ;; Follows Expectat io n: (and (numberp x) (cos x)) (and (numberp x) (> x 3)) (if (numberp x) (cos x)) (if (numberp x) (cos x) nil) (if (numberp x) (print x)) (when (numberp x) (print x)) 13

  2. 1.2. Ho w do I kno w if it's go o d? Be Concise T est fo r the simplest case. If y ou mak e the same test (o r return the same result) in t w o places, there must b e an easier w a y . Bad: verb ose, convoluted (defun count-al l-n um ber s (alist) (cond ((null alist) 0) (t (+ (if (listp (first alist)) (count-a ll- nu mbe rs (first alist)) (if (numberp (first alist)) 1 0)) (count-a ll- nu mb ers (rest alist)) )) )) � Returns 0 t wice � Nonstanda rd indentation � alist suggests asso ciation list Go o d: (defun count-al l-n um ber s (exp) (typecas e exp (cons (+ (count-all -nu mb er s (first exp)) (count-all -nu mb er s (rest exp)))) (number 1) (t 0))) cond instead of typecase is equally go o d (less sp eci�c, mo re conventional, consistent). 14

  3. 1.2. Ho w do I kno w if it's go o d? Be Concise Maximize LOCNW: lines of co de not written. \Sho rter is b etter and sho rtest is b est." { Jim Meehan Bad: to o verb ose, ine�cient (defun vector-a dd (x y) (let ((z nil) n) (setq n (min (list-le ngt h x) (list-len gth y))) (dotimes (j n (reverse z)) (setq z (cons (+ (nth j x) (nth j y)) z))))) (defun matrix-a dd (A B) (let ((C nil) m) (setq m (min (list-le ngt h A) (list-len gth B))) (dotimes (i m (reverse C)) (setq C (cons (vector-ad d (nth i A) (nth i B)) C))))) 2 � Use of nth mak es this O ( n ) � Why list-len gth ? Why not length o r mapcar ? � Why not nreverse ? � Why not use a rra ys to implement a rra ys? � The return value is hidden 15

  4. 1.2. Ho w do I kno w if it's go o d? Be Concise Better: mo re concise (defun vector-a dd (x y) "Element -wi se add of two vectors" (mapcar #'+ x y)) (defun matrix-a dd (A B) "Element -wi se add of two matrices (lists of lists)" (mapcar #'vector-a dd A B)) Or use generic functions: (defun add (&rest args) "Generic addition" (if (null args) 0 (reduce #'binary- ad d args))) (defmeth od binary-ad d ((x number) (y number)) (+ x y)) (defmeth od binary-ad d ((x sequence) (y sequence )) (map (type-of x) #'binary- ad d x y)) 16

  5. 1.2. Ho w do I kno w if it's go o d? Be Helpful Do cumentation should b e o rganized a round tasks the user needs to do, not a round what y our p rogram hap- p ens to p rovide. Adding do cumentation strings to each function usually do esn't tell the reader ho w to use y our p rogram, but hints in the right place can b e very ef- fective. Go o d: (from Gnu Emacs online help) next-lin e: Move curso r vertically do wn ARG lines. : : : If y ou a re thinking of using this in a Lisp p rogram, consider using `fo rw a rd-line' instead. It is usually eas- ier to use and mo re reliable (no dep endence on goal column, etc.). defun: de�nes NAME as a function. The de�nition is (lambda ARGLIST [DOCSTRIN G] BODY...) . See also the function interactiv e . These anticipate user's use and p roblems. 17

  6. 1.2. Ho w do I kno w if it's go o d? Be Conventional Build y our o wn functionalit y to pa rallel existing features Ob ey naming conventions: with- somethi ng , do something macros Use built-in functionalit y when p ossible � Conventional: reader will kno w what y ou mean � Concise: reader do esn't have to pa rse the co de � E�cient: has b een w o rk ed on heavily Bad: non-conventional (defun add-to-l ist (elt list) (cond ((member elt lst) lst) (t (cons elt lst)))) Go o d: use a built-in function (left as an exercise) \Use lib ra ry functions" { Kernighan & Plauger 18

  7. 1.2. Ho w do I kno w if it's go o d? Be Consistent Some pairs of op erato rs have overlapping capabiliti es. Be consistent ab out which y ou use in neutral cases (where either can b e used), so that it is appa rent when y ou're doing something unusual. Here a re examples involving let and let* . The �rst exploits pa rallel binding and the second sequential. The third is neutral. (let ((a b) (b a)) ...) (let* ((a b) (b (* 2 a)) (c (+ b 1))) ...) (let ((a (* x (+ 2 y))) (b (* y (+ 2 x)))) ...) Here a re analogous examples using flet and labels . The �rst exploits closure over the lo cal function, the second exploits non-closure. The third is neutral. (labels ((process (x) ... (process (cdr x)) ...)) ...) (flet ((foo (x) (+ (foo x) 1))) ...) (flet ((add3 (x) (+ x 3))) ...) In b oth cases, y ou could cho ose things the other w a y a round, alw a ys using let* o r labels in the neutral case, and let o r flet in the unusual case. Consistency mat- ters mo re than the actual choice. Most p eople, ho w- ever, think of let and flet as the no rmal choices. 19

  8. 1.2. Ho w do I kno w if it's go o d? Cho ose the Right Language Cho ose the app rop riate language, and use app rop riate features in the language y ou cho ose. Lisp is not the right language fo r every p roblem. \Y ou got to dance with the one that b rung y ou." { Bea r Bry ant Lisp is go o d fo r: � Explo rato ry p rogramm ing � Rapid p rotot yping � Minimizi ng time-to-ma rk et � Single-p rogram m er (o r single-digit team) p rojects � Source-to-source o r data-to-data transfo rmation Compilers and other translato rs Problem-sp eci�c languages � Dynamic dispatch and creation (compiler available at run-time) � Tight integration of mo dules in one image (as opp osed to Unix's cha racter pip e mo del) � High degree of interaction (read-eval-p rint, CLIM) � User-extensible applications (gnu emacs) \I b elieve go o d soft w a re is written b y small teams of t w o, three, o r four p eople interacting with each other at a very high, dense level." { John W a rno ck 20

  9. 1.2. Ho w do I kno w if it's go o d? Cho ose the Right Language \Once y ou a re an exp erienced Lisp p rogrammer, it's ha rd to return to any other language." { Rob ert R. Kessler Current Lisp implem entations a re not so go o d fo r: � P ersistent sto rage (data base) � Maximizing resource use on small machines � Projects with hundreds of p rogrammers � Close communication with fo reign co de � Delivering small-i m age applications � Real-time control (but Gensym did it) � Projects with inexp erienced Lisp p rogrammer s � Some kinds of numerical o r cha racter computation (W o rks �ne with ca reful decla rations, but the Lisp e�ciency mo del is ha rd to lea rn.) 21

  10. 2. Tips on Built-in F unctionalit y Built-in F unctionalit y \No doubt ab out it, Common Lisp is a big language" { Guy Steele � 622 built-in functions (in one p re-ANSI CL) � 86 macros � 27 sp ecial fo rms � 54 va riables � 62 constants But what counts as the language itself ? � C++ has 48 reserved w o rds � ANSI CL is do wn to 25 sp ecial fo rms � The rest can b e thought of as a required lib ra ry Either w a y , the Lisp p rogrammer needs some help: Which built-in functionalit y to mak e use of Ho w to use it 22

  11. 2. Tips on Built-in F unctionalit y DEFV AR and DEFP ARAMETER Use defvar fo r things y ou don't w ant to re-initia li ze up on re-load. (defvar *options* '()) (defun add-opti on (x) (pushnew x *options* )) Here y ou might have done (add-optio n ...) many times b efo re y ou re-load the �le{p erhaps some even from another �le. Y ou usually don't w ant to thro w a w a y all that data just b ecause y ou re-load this de�ni- tion. On the other hand, some kinds of options do w ant to get re-initial i zed up on re-load... (defpara me ter *use-expe ri men ta l- mod e* nil "Set this to T when experime nt al code works.") Later y ou might edit this �le and set the va riable to T, and then re-load it, w anting to see the e�ect of y our edits. Recommendation: Igno re the pa rt in CLtL that sa ys defvar is fo r va riables and defparam et er is fo r pa rame- ters. The only useful di�erence b et w een these is that defvar do es its assignment only if the va riable is un- b ound, while defparam ete r do es its assignment uncon- ditionall y . 23

  12. 2. Tips on Built-in F unctionalit y EV AL-WHEN (eval-wh en (:execute ) ...) = (eval-whe n (:compile- top le vel ) ...) + (eval-whe n (:load-top le vel ) ...) Also, tak e ca re ab out explicitly nesting eval-when fo rms. The e�ect is not generally intuitive fo r most p eople. 24

  13. 2. Tips on Built-in F unctionalit y FLET to Avoid Co de Duplication Consider the follo wing example's duplicated use of (f (g (h))) . (do ((x (f (g (h))) (f (g (h))))) (nil) ...) Every time y ou edit one of the (f (g (h))) 's, y ou p rob- ab y w ant to edit the other, to o. Here is a b etter mo d- ula rit y: (flet ((fgh () (f (g (h))))) (do ((x (fgh) (fgh))) (nil) ...)) (This might b e used as an a rgument against do .) Simil a rl y , y ou might use lo cal functions to avoid dupli- cation in co de b ranches that di�er only in their dynamic state. F o r example, (defmacr o handler-ca se- if (test form &rest cases) (let ((do-it (gensym "DO-IT")) ) `(flet ((,do-it () ,form)) (if test (handler- cas e (,do-it) ,@cases) (,do-it)) ))) 25

  14. 2. Tips on Built-in F unctionalit y DEFP A CKA GE Programmi ng in the la rge is supp o rted b y a design st yle that sepa rates co de into mo dules with clea rly de�ned interfaces. The Common Lisp pack age system serves to avoid name clashes b et w een mo dules, and to de�ne the in- terface to each mo dule. � There is no top level (b e thread-safe) � There a re other p rograms (use pack ages) � Mak e it easy fo r y our consumers Exp o rt only what the consumer needs � Mak e it easy fo r maintai ners License to change non-exp o rted pa rt (defpack ag e "PARSER" (:use "LISP" #+Lucid "LCL" #+Allegr o "EXCL") (:export "PARSE" "PARSE-FI LE " "START-PAR SER -W IN DOW " "DEFINE-G RAM MA R" "DEFINE-T OKE NI ZER ") ) Some put exp o rted symb ols at the top of the �le where they a re de�ned. W e feel it is b etter to put them in the defpackage , and use the edito r to �nd the co rresp onding de�nitions. 26

  15. 2. Tips on Built-in F unctionalit y Understanding Conditions vs Erro rs Lisp assures that most erro rs in co de will not co rrupt data b y p roviding an active condition system. Lea rn the di�erence b et w een erro rs and conditions . All erro rs a re conditions; not all conditions a re erro rs. Distinguish three concepts: � Signaling a condition| Detecting that something unusual has happ ened. � Providing a resta rt| Establishing one of p ossibly several options fo r continuing. � Handling a condition| Selecting ho w to p ro ceed from availabl e options. 27

  16. 2. Tips on Built-in F unctionalit y Erro r Detection Pick a level of erro r detection and handling that matches y our intent. Usually y ou don't w ant to let bad data go b y , but in many cases y ou also don't w ant to b e in the debugger fo r inconsequential reasons. Strik e a balance b et w een tolerance and pickiness that is app rop riate to y our application. Bad: what if its not an integer? (defun parse-da te (string) "Read a date from a string. ..." (multipl e-v al ue- bi nd (day-of-m ont h string-pos it ion ) (parse-in teg er string :junk-all owe d t) ...)) Questionable: what if memo ry runs out? (ignore- er ror s (parse-dat e string)) Better: catches exp ected erro rs only (handler- cas e (parse-dat e string) (parse-err or nil)) 28

  17. 2. Tips on Built-in F unctionalit y W riting Go o d Erro r Messages � Use full sentences in erro r messages (upp ercase initial, trailing p erio d). � No "Error: " o r ";;" p re�x. The system will sup- ply such a p re�x if needed. � Do not b egin an erro r message with a request fo r a fresh line. The system will do this automaticall y if necessa ry . � As with other fo rmat strings, don't use emb edded tab cha racters. � Don't mention the consequences in the erro r mes- sage. Just describ e the situation itself. � Don't p resupp ose the debugger's user interface in describing ho w to continue. This ma y cause p o rta- bilit y p roblems since di�erent implementati ons use di�erent interfaces. Just describ e the abstract ef- fect of a given action. � Sp ecify enough detail in the message to distinguish it from other erro rs, and if y ou can, enough to help y ou debug the p roblem later if it happ ens. 29

  18. 2. Tips on Built-in F unctionalit y W riting Go o d Erro r Messages (cont'd) Bad: (error "~%>> Error: Foo. Type :C to continue. ") Better: (cerror "Specify a replaceme nt sentence interacti vel y. " "An ill-form ed sentence was encounter ed :~ % ~A" sentence) 30

  19. 2. Tips on Built-in F unctionalit y Using the Condition System Sta rt with these: � erro r, cerro r � w a rn � handler-case � with-simple-resta rt � unwind-p rotect Go o d: standa rd use of w a rn (defvar *word* '?? "The word we are currently working on.") (defun lex-warn (format-s tr &rest args) "Lexical warning; like warn, but first tells what word caused the warning." (warn "For word ~a: ~?" *word* format-st r args)) 31

  20. 2. Tips on Built-in F unctionalit y HANDLER-CASE, WITH-SIMPLE-REST ART Go o d: handle sp eci�c erro rs (defun eval-exp (exp) "If possible evaluate this exp; otherwis e return it." ;; Guard against errors in evaluati ng exp (handler -ca se (if (and (fboundp (op exp)) (every #'is-const an t (args exp))) (eval exp) exp) (arithmet ic -er ro r () exp))) Go o d: p rovide resta rts (defun top-leve l (&key (prompt "=> ") (read #'read) (eval #'eval) (print #'print) ) "A read-eva l- pri nt loop." (with-si mpl e- res ta rt (abort "Exit out of the top level.") (loop (with-si mp le- re sta rt (abort "Return to top level loop.") (format t "~&~a" prompt) (funcall print (funcall eval (funcall read))))) )) 32

  21. 2. Tips on Built-in F unctionalit y UNWIND-PROTECT unwind-p ro tec t implem ents imp o rtant functionalit y that every one should kno w ho w to use. It is not just fo r sys- tem p rogramm ers. W atch out fo r multi-taski ng, though. F o r example, im- plementing some kinds of state-binding with unwind-pr ot ect might w o rk w ell in a single-threaded environment, but in an environment with multi-task i ng, y ou often have to b e a little mo re ca reful. (unwind- pr ote ct (progn fo rm fo rm ... fo rm ) n 1 2 cleanup cleanup ... cleanup ) n 1 2 � Never assume form will get run at all. 1 � Never assume form w on't run to completion. n 33

  22. 2. Tips on Built-in F unctionalit y UNWIND-PROTECT (cont'd) Often y ou need to save state b efo re entering the unwind-p ro tec t , and test b efo re y ou resto re state: P ossibly Bad: (with multi-taski ng) (catch 'robot-o p (unwind- pro te ct (progn (turn-on- mot or ) (manipula te) ) (turn-off -m oto r) )) Go o d: (safer) (catch 'robot-o p (let ((status (motor-st atu s motor))) (unwind-p ro tec t (progn (turn-on- mot or motor) (manipula te motor)) (when (motor-o n? motor) (turn-off- mo tor motor)) (setf (motor-s tat us motor) status))) ) 34

  23. 2. Tips on Built-in F unctionalit y I/O Issues: Using F ORMA T � Don't use T ab cha racters in fo rmat strings (o r any strings intended fo r output). Dep ending on what column y our output sta rts in, the tab stops ma y not line up the same on output as they did in the co de! � Don't use "#<~S ~A>" to p rint unreadable objects. Use print-unr ea dab le -ob je ct instead. � Consider putting fo rmat directives in upp ercase to mak e them stand out from lo w ercase text sur- rounding. F o r example, "Foo: ~A" instead of "Foo: ~a" . � Lea rn useful idioms. F o r example: ~{~A~^, ~} and ~:p . � Be conscious of when to use ~& versus ~% . Also, "~2%" and "~2&" a re also handy . Most co de which outputs a single line should sta rt with ~& and end with ~% . (format t "~&This is a test.~%") This is a test. � Be a w a re of implementati on extensions. They ma y not b e p o rtable, but fo r non-p o rtable co de might b e very useful. F o r example, Genera's ! and fo r handling indentation. 35

  24. 2. Tips on Built-in F unctionalit y Using Streams Co rrectly � *standard- ou tpu t* and *standard- in put * vs *terminal- io * Do not assume *standard -in pu t* and *standar d- out pu t* will b e b ound to *terminal -io * (o r, in fact, to any interactive stream). Y ou can bind them to such a stream, ho w ever. T ry not to use *terminal -i o* directly fo r input o r output. It is p rima ri l y availabl e as a stream to which other streams ma y b e b ound, o r ma y indi- rect ( e.g., b y synonym streams). � *error-out pu t* vs *debug-io* Use *error-ou tp ut * fo r w a rnings and erro r mes- sages that a re not accompanied b y any user inter- action. Use *debug-io * fo r interactive w a rnings, erro r mes- sages, and other interactions not related to the no rmal function of a p rogram. In pa rticula r, do not �rst p rint a message on *error-ou tpu t* and then do a debugging session on *debug-io* , ex- p ecting those to b e the same stream. Instead, do each interaction consistently on one stream. 36

  25. 2. Tips on Built-in F unctionalit y Using Streams Co rrectly (cont'd) � *trace-out pu t* This can b e used fo r mo re than just receiving the output of trace . If y ou write debugging routines that conditionally p rint helpful info rmation with- out stopping y our running p rogram, consider do- ing output to this stream so that if *trace-ou tp ut * is redirected, y our debugging output will to o. A useful test: If someone re-b ound only one of several I/O streams y ou a re using, w ould it mak e y our output lo ok stupid? 37

  26. 3. Tips on Nea r-Standa rd T o ols Using Nea r-Standa rd T o ols Some functionalit y is not built in to the language, but is used b y most p rogrammers. This divides into exten- sions to the language and to ols that help y ou develop p rograms. Extensions � defsystem to de�ne a p rogram � CLIM , CLX , etc. graphics lib ra ri es T o ols � emacs from FSF, Lucid indentation, font/colo r supp o rt de�nition/a rglist/do c/regexp �nding communication with lisp � xref , manual , etc. from CMU � Bro wsers, debuggers, p ro�lers from vendo rs 38

  27. 3. Tips on Nea r-Standa rd T o ols DEFSYSTEM Pick a public domain version of defsyste m (unfo rtu- nately , dpANS CL has no standa rd). � Put absolute pathnames in one place only � Load everything through the defsystem � Distinguish compiling from loading � Optionally do version control (defpack ag e "PARSER" ...) (defsyst em parser (:source "/lab/ind exi ng /pa rs er /*" ) (:parts utilities "macros" "grammar " "tokeniz er " "optimizer " "debugge r" "toplevel " #+CLIM "clim-gr aph ic s" #+CLX "clx-gra ph ics ") ) � Mak e sure y our system loads with no compiler w a rnings (�rst time and subsequent times) (lea rn to use (declare (ignore ...)) ) � Mak e sure the system can b e compiled from scratch (eliminate lingering b o otstrapping p roblems) 39

  28. 3. Tips on Nea r-Standa rd T o ols Edito r Commands Y our edito r should b e able to do the follo wing: � Move ab out b y s-exp ressions and sho w matching pa rens � Indent co de p rop erly � Find unbalanced pa rens � Ado rn co de with fonts and colo rs � Find the de�nition of any symb ol � Find a rguments o r do cumentation fo r any symb ol � Macro expand any exp ression � Send the current exp ression, region o r �le to Lisp to b e evaluated o r compiled � Keep a histo ry of commands sent to Lisp and allo w y ou to edit and resend them � W o rk with k eyb oa rd, mouse, and menus Emacs can do all these things. If y our edito r can't, complain until it is �xed, o r get a new one. 40

  29. 3. Tips on Nea r-Standa rd T o ols Emacs: Indentation and Comments Don't try to indent y ourself. Instead, let the edito r do it. A nea r-standa rd fo rm has evolved. � 80-column maximum width � Ob ey comment conventions ; fo r inline comment ;; fo r in-function comment ;;; fo r b et w een-function comment ;;;; fo r section header (fo r outline mo de) � cl-indent lib ra ry can b e told ho w to indent (put 'defvar 'common-l is p-i nd ent -f unc ti on '(4 2 2)) � lemacs can p rovide fonts, colo r (hilit::mo de s-l is t-u pd at e "Lisp" '((";;.*" nil hilit2) ...)) 41

  30. 4. Abstraction Abstraction All p rogramm ing languages allo w the p rogrammer to de�ne abstractions . All mo dern languages p rovide sup- p o rt fo r: � Data Abstraction (abstract data t yp es) � F unctional Abstraction (functions, p ro cedures) Lisp and other languages with closures ( e.g., ML, Sather) supp o rt: � Control Abstraction (de�ning iterato rs and other new �o w of control constructs) Lisp is unique in the degree to which it supp o rts: � Syntactic Abstraction (macros, whole new lan- guages) 42

  31. 4. Abstraction Design: Where St yle Begins \The most imp o rtant pa rt of writing a p rogram is de- signing the data structures. The second most imp o r- tant pa rt is b reaking the va rious co de pieces do wn." { Bill Gates \Exp ert engineers stratify complex designs. : : : The pa rts constructed at each level a re used as p rimi tives at the next level. Each level of a strati�ed design can b e thought of as a sp ecialized language with a va riet y of p rimi tives and means of combination app rop riate to that level of detail." { Ha rold Ab elson and Gerald Sussman \Decomp ose decisions as much as p ossible. Untangle asp ects which a re only seemingly indep endent. Defer those decisions which concern details of rep resentation as long as p ossible." { Niklaus Wirth Lisp supp o rts all these app roaches: � Data Abstraction: classes, structures, deft yp e � F unctional Abstraction: functions, metho ds � Interface Abstraction: pack ages, closures � Object-Oriented: CLOS, closures � Strati�ed Design: closures, all of ab ove � Dela y ed Decisions: run-time dispatch 43

  32. 4. Abstraction Design: Decomp osition \A Lisp p ro cedure is lik e a pa ragraph." { Deb o rah T ata r \Y ou should b e able to explain any mo dule in one sen- tence." { W a yne Ratli� � Strive fo r simple designs � Break the p roblem into pa rts Design useful subpa rts (strati�ed) Be opp o rtunistic; use existing to ols � Determine dep endencies Re-mo dula rize to reduce dep endencies Design most dep endent pa rts �rst W e will cover the follo wing kinds of abstraction: � Data abstraction � F unctional abstraction � Control abstraction � Syntactic abstraction 44

  33. 4.1. Data Abstraction Data Abstraction W rite co de in terms of the p roblem's data t yp es, not the t yp es that happ en to b e in the implementati on. � Use defstruct o r defclass fo r reco rd t yp es � Use inline functions as aliases (not macros) � Use deftype � Use decla rations and :type slots fo r e�ciency and/o r do cumentation � V a riable names give info rmal t yp e info rmation Prett y Go o d: sp eci�es some t yp e info (defclas s event () ((starti ng- ti me :type integer) (location :type location) (duration :type integer :initfor m 0))) Better: p roblem-sp eci�c t yp e info (deftype time () "Time in seconds" 'integer) (defcons ta nt +the-dawn- of -ti me + 0 "Midnigh t, January 1, 1900" (defclas s event () ((starti ng- ti me :type time :initfor m +the-daw n- of- ti me+ ) (location :type location) (duration :type time :initfor m 0))) 45

  34. 4.1. Data Abstraction Use Abstract Data T yp es Intro duce abstract data t yp es with accesso rs: Bad: obscure accesso r, eval (if (eval (cadar rules)) ...) Better: intro duce names fo r accesso rs (declaim (inline rule-ant ece de nt )) (defun rule-ant ece de nt (rule) (second rule)) (if (holds? (rule-an tec ed ent (first rules))) ...) Usually Best: intro duce �rst-class data t yp e (defstru ct rule name antecede nt consequent ) o r (defstru ct (rule (:type list)) name antecede nt consequent ) o r (defclas s rule () (name antecedent consequen t) ) 46

  35. 4.1. Data Abstraction Impleme nt Abstract Data T yp es Kno w ho w to map from common abstract data t yp es to Lisp implementati ons. � Set: list, bit-vecto r, integer, any table t yp e � Sequence: list, vecto r, dela y ed-evaluation stream � Stack: list, vecto r (with �ll-p ointer) � Queue: tconc, vecto r (with �ll-p ointer) � T able: hash table, alist, plist, vecto r � T ree, Graph: cons, structures, vecto r, adjacency matrix Use implem entati ons that a re already supp o rted (e.g. union, intersec tio n, length fo r sets as lists; logior, logand, logcount fo r sets as integers. Don't b e afraid to build a new implem entati on if p ro- �ling reveals a b ottleneck. (If Common Lisp's hash tables a re to o ine�cient fo r y our application, consider building a sp ecialized hash table in Lisp b efo re y ou build a sp ecialized hash table in C.) 47

  36. 4.1. Data Abstraction Inherit from Data T yp es Reuse b y inheritance as w ell as direct use � structures supp o rt single inheritance � classes supp o rt multiple inheritance � b oth allo w some over-riding � classes supp o rt mixins Consider a class o r structure fo r the whole p rogram � Elimi nates clutter of global va riables � Thread-safe � Can b e inherited and mo di�ed 48

  37. 4.2. F unctional Abstraction F unctional Abstraction Every function should have: � A single sp eci�c purp ose � If p ossible, a generally useful purp ose � A meaningful name (names lik e recurse-a ux indicate p roblems) � A structure that is simple to understand � An interface that is simple y et general enough � As few dep endencies as p ossible � A do cumentation string 49

  38. 4.2. F unctional Abstraction Decomp osition Decomp ose an algo rithm into functions that a re simple, meaningful and useful. Example from comp.lang.li sp discussion of loop vs. map : (defun least-co mmo n- sup er cla ss (instance s) (let ((candid ate s (reduce #'inters ect io n (mapcar #'(lambda (instance) (clos:cl ass -p rec ed en ce- li st (class-of instance) )) instances) )) (best-cand id ate (find-cl ass t))) (mapl #'(lambd a (candidate s) (let ((current -c and id at e (first candidat es) ) (remaining -ca nd id ate s (rest candidate s) )) (when (and (subtype p current-ca ndi da te best-candi dat e) (every #'(lambda (remainin g-c an di dat e) (subtype p current-ca nd ida te remaining- ca ndi da te) ) remaining -ca nd ida te s)) (setf best-cand id at e current- can di da te) )) ) candidat es ) best-cand id ate )) 50

  39. 4.2. F unctional Abstraction Decomp osition V ery Go o d: Chris Riesb eck (defun least-co mmo n- sup er cla ss (instance s) (reduce #'more-spe cif ic -cl as s (common-su per cl ass es instances ) :initial-v alu e (find-clas s 't))) (defun common-s upe rc las se s (instanc es ) (reduce #'intersec tio n (superclas s-l is ts instances ))) (defun supercla ss- li sts (instanc es) (loop for instance in instance s collect (clos:cla ss- pr ec ede nc e-l is t (class-o f instance )) )) (defun more-spe cif ic -cl as s (class1 class2) (if (subtypep class2 class1) class2 class1)) � Each function is very understandable � Control structure is clea r: Tw o reduces, an intersection and a lo op/collect � But reusablit y is fairly lo w 51

  40. 4.2. F unctional Abstraction Decomp osition Equally Go o d: and mo re reusable (defun least-co mmo n- sup er cla ss (instance s) "Find a least class that all instances belong to." (least-u ppe r- bou nd (mapcar #'class- of instances ) #'clos:cl as s-p re ced en ce- li st #'subtype p) ) (defun least-up per -b oun d (elements supers sub?) "Element of lattice that is a super of all elements. " (reduce #'(lambda (x y) (binary-l ea st- up pe r-b ou nd x y supers sub?)) elements)) (defun binary-l eas t- upp er -bo un d (x y supers sub?) "Least upper bound of two elements. " (reduce- if sub? (intersect io n (funcall supers x) (funcall supers y)))) (defun reduce-i f (pred sequence) "E.g. (reduce-if #'> numbers) computes maximum" (reduce #'(lambda (x y) (if (funcall pred x y) x y)) sequence)) � Individual functions remain understandable � Still 2 reduces, an intersection and a map ca r � Strati�ed design yields mo re useful functions 52

  41. 4.2. F unctional Abstraction Rule of English T ranslation T o insure that y ou sa y what y ou mean: 1. Sta rt with an English description of algo rithm 2. W rite the co de from the description 3. T ranslate the co de back into English 4. Compa re 3 to 1 Example: 1. \Given a list of monsters, determine the numb er that a re sw a rms." 2. (defun count-swar m (monster -l ist ) (apply '+ (mapcar #'(lambda (monster) (if (equal (object-t ype (get-obj ect monster) ) 'swarm) 1 0)) monster-li st )) ) 3. \T ak e the list of monsters and p ro duce a 1 fo r a monster whose t yp e is sw a rm, and a 0 fo r the others. Then add up the list of numb ers." 53

  42. 4.2. F unctional Abstraction Rule of English T ranslation Better: 1. \Given a list of monsters, determine the numb er that a re sw a rms." 2. (defun count-swar ms (monster- nam es ) "Count the swarms in a list of monster names." (count-if #'swarm-p monster-na me s :key #'get-ob je ct) ) o r (count 'swarm monster- na mes :key #'get-obj ec t- typ e) o r (loop for name in monster-n am es count (swarm-p (get-obj ect monster)) ) 3. \Given a list of monster names, count the numb er that a re sw a rms." 54

  43. 4.2. F unctional Abstraction Use Lib ra ry F unctions Lib ra ri es ma y have access to lo w-level e�ciency hacks, and a re often �ne-tuned. BUT they ma y b e to o general, hence ine�cient. W rite a sp eci�c version when e�ciency is a p roblem. Go o d: sp eci�c, concise (defun find-cha rac te r (char string) "See if the character appears in the string." (find char string)) Go o d: e�cient (defun find-cha rac te r (char string) "See if the character appears in the string." (declare (characte r char) (simple-s tri ng string)) (loop for ch across string when (eql ch char) return ch)) 55

  44. 4.2. F unctional Abstraction Use Lib ra ry F unctions Given build1 , which maps n to a list of n x 's: (build1 4) ) (x x x x) T ask: De�ne build-it so that: (build-i t '(4 0 3)) ) ((x x x x) () (x x x)) Incredibly Bad: (defun round3 (x) (let ((result '())) (dotimes (n (length x) result) (setq result (cons (car (nthcdr n x)) result)) )) ) (defun build-it (arg-list ) (let ((result '())) (dolist (a (round3 arg-list) result) (setq result (cons (build1 a) result))) )) Problems: � round3 is just another name fo r reverse � (car (nthcdr n x)) is (nth n x) � dolist w ould b e b etter than dotimes here � push w ould b e app rop riate here � (mapcar #'build1 numbers) do es it all 56

  45. 4.3. Control Abstraction Control Abstraction Most algo rithm s can b e cha racterized as: � Sea rching ( some find find-if mismatch ) � So rting ( sort merge remove-dup lic at es ) � Filtering ( remove remove-if mapcan ) � Mapping ( map mapcar mapc ) � Combining ( reduce mapcan ) � Counting ( count count-if ) These functions abstract common control patterns. Co de that uses them is: � Concise � Self-do cumenting � Easy to understand � Often reusable � Usually e�cient (Better than a non-tail recursion) Intro ducing y our o wn control abstraction is an imp o r- tant pa rt of strati�ed design. 57

  46. 4.3. Control Abstraction Recursion vs. Iteration Recursion is go o d fo r recursive data structures. Many p eople p refer to view a list as a sequence and use iter- ation over it, thus de-emphasizing the implementati on detail that the list is split into a �rst and rest. As an exp ressive st yle, tail recursion is often considered elegant. Ho w ever, Common Lisp do es not gua rantee tail recursion elimi nati on so it should not b e used as a substitute fo r iteration in completely p o rtable co de. (In Scheme it is �ne.) The Common Lisp do macro can b e thought of as syn- tactic suga r fo r tail recursion, where the initial values fo r va riables a re the a rgument values on the �rst func- tion call, and the step values a re a rgument values fo r subsequent function calls. do p rovides a lo w level of abstraction, but versatile and has a simple, explicit execution mo del. 58

  47. 4.3. Control Abstraction Recursion vs. Iteration (cont'd) Bad: (in Common Lisp) (defun any (lst) (cond ((null lst) nil) ((car lst) t) (t (any (cdr lst))))) Better: conventional, concise (defun any (list) "Return true if any member of list is true." (some #'not-null list)) o r (find-if -no t #'null lst) o r (loop for x in list thereis x) o r (explicit) (do ((list list (rest list))) ((null list) nil) (when (first list)) (return t)))) Best: e�cient, most concise in this case Don't call any at all! Use (some p list) instead of (any (mapcar p list)) 59

  48. 4.3. Control Abstraction LOOP \Keep a loop to one topic|lik e a letter to y our Senato r." { Judy Anderson The Common Lisp loop macro gives y ou the p o w er to exp ress idiomati c usages concisely . Ho w ever it b ea rs the burden that its syntax and semantics a re often sub- stantially mo re complex than its alternatives. Whether o r not to use the loop macro is an issue sur- rounded in controversy , and b o rders on a religious w a r. A t the ro ot of the con�ict is the follo wing somewhat pa rado xical observation: � loop app eals to naive p rogrammers b ecause it lo oks lik e English and seems to call fo r less kno wledge of p rogrammi ng than its alternatives. � loop is not English; its syntax and semantics have subtle intricacies that have b een the source of many p rogrammi ng bugs. It is often b est used b y p eople who've tak en the time to study and un- derstand it|usually not naive p rogrammers. Use the unique features of lo op ( e.g., pa rallel iteration of di�erent kinds). 60

  49. 4.3. Control Abstraction Simple Iteration Bad: verb ose, control structure unclea r (LOOP (SETQ *WORD* (POP *SENTENCE* )) ;get the next word (COND ;; if no more words then return instantiat ed CD form ;; which is stored in the variable *CONCEPT* ((NULL *WORD*) (RETURN (REMOVE- VAR IA BLE S (VAR-VALUE '*CONCEP T*) )) ) (T (FORMAT T "~%~%Proc ess in g ~A" *WORD*) (LOAD-DEF ) ; look up requests under ; this word (RUN-STAC K)) )) ) ; fire requests � No need fo r global va riables � End test is misleadi ng � Not immedia tely clea r what is done to each w o rd Go o d: conventional, concise, explicit (mapc #'process- wo rd sentence) (remove- var ia ble s (var-value '*concept *) ) (defun process- wor d (word) (format t "~2%Proc ess in g ~A" word) (load-de f word) (run-sta ck) ) 61

  50. 4.3. Control Abstraction Mapping Bad: verb ose ; (extract -id -l ist 'l_user-r ec s) --------- --- - [lambda] ; WHERE: l_user-re cs is a list of user records ; RETURNS: a list of all user id's in l_user-re cs ; USES: extract-i d ; USED BY: process-u ser s, sort-user s (defun extract- id- li st (user-rec s) (prog (id-list) loop (cond ((null user-recs) ;; id-list was construc te d in reverse order ;; using cons, so it must be reversed now: (return (nreverse id-list))) ) (setq id-list (cons (extract-i d (car user-recs) ) id-list)) (setq user-recs (cdr user-recs )) ;next user record (go loop))) Go o d: conventional, concise (defun extract- id- li st (user-rec ord -l ist ) "Return the user ID's for a list of users." (mapcar #'extract- id user-reco rd- li st) ) 62

  51. 4.3. Control Abstraction Counting Bad: verb ose (defun size () (prog (size idx) (setq size 0 idx 0) loop (cond ((< idx table-siz e) (setq size (+ size (length (aref table idx))) idx (1+ idx)) (go loop))) (return size))) Go o d: conventional, concise (defun table-co unt (table) ; Formerly called SIZE "Count the number of keys in a hash-like table." (reduce #'+ table :key #'length)) Also, it couldn't hurt to add: (deftype table () "A table is a vector of buckets, where each bucket holds an alist of (key . values) pairs." '(vector cons)) 63

  52. 4.3. Control Abstraction Filtering Bad: verb ose (defun remove-b ad- pr ed- vi sit ed (l badpred closed) ;;; Returns a list of nodes in L that are not bad ;;; and are not in the CLOSED list. (cond ((null l) l) ((or (funcall badpred (car l)) (member (car l) closed)) (remove-b ad -pr ed -vi si te d (cdr l) badpred closed)) (t (cons (car l) (remove- bad -p re d-v is ite d (cdr l) badpred closed)) ))) Go o d: conventional, concise (defun remove-b ad- or -cl os ed- no de s (nodes bad-node ? closed) "Remove nodes that are bad or are on closed list" (remove- if #'(lambda (node) (or (funcall bad-node? node) (member node closed)) ) nodes)) 64

  53. 4.3. Control Abstraction Control Flo w: Keep It Simple Non-lo cal control �o w is ha rd to understand Bad: verb ose, violates referential transpa rency (defun isa-test (x y n) (catch 'isa (isa-test 1 x y n))) (defun isa-test 1 (x y n) (cond ((eq x y) t) ((member y (get x 'isa)) (throw 'isa t)) ((zerop n) nil) (t (any (mapcar #'(lambd a (xx) (isa-test xx y (1- n)) ) (get x 'isa) ))) ) ) Problems: � catch/thro w is gratuitous � member test ma y o r ma y not b e helping � mapcar generates ga rbage � any tests to o late; throw tries to �x this result is that any never gets called! 65

  54. 4.3. Control Abstraction Keep It Simple Some recommendations fo r use of catch and throw : � Use catch and throw as sub-p rimi ti ves when imple- menting mo re abstract control structures as macros, but do not use them in no rmal co de. � Sometimes when y ou establish a catch, p rograms ma y need to test fo r its p resence. In that case, resta rts ma y b e mo re app rop riate. 66

  55. 4.3. Control Abstraction Keep It Simple Go o d: (defun isa-test (sub super max-dept h) "Test if SUB is linked to SUPER by a chain of ISA links shorter than max-depth ." (and (>= max-depth 0) (or (eq sub super) (some #'(lambd a (parent) (isa-test parent super (- max-dept h 1))) (get sub 'isa))))) Also go o d: uses to ols (defun isa-test (sub super max-dept h) (depth-f irs t- sea rc h :start sub :goal (is super) :success or s #'get-is a :max-dep th max-depth )) \W rite clea rly|don't b e to o clever." { Kernighan & Plauger Be Aw a re: Do es \imp roving" something change the semantics? Do es that matter? 67

  56. 4.3. Control Abstraction Avoid Complicated Lamb da Exp ressions When a higher-o rder function w ould need a compli- cated lamb da exp ression, consider alternatives: � dolist o r loop � generate an intermediate (ga rbage) sequence � Series � Macros o r read macros � lo cal function { Sp eci�c: mak es it clea r where function is used { Do esn't clutter up global name space { Lo cal va riables needn't b e a rguments { BUT: some debugging to ols w on't w o rk 68

  57. 4.3. Control Abstraction Avoid Complicated Lamb da Exp ressions Find the sum of the squa res of the o dd numb ers in a list of integers: All Go o d: (reduce #'+ numbers :key #'(lambda (x) (if (oddp x) (* x x) 0))) (flet ((square- odd (x) (if (oddp x) (* x x) 0))) (reduce #'+ numbers :key #'square -o dd) ) (loop for x in list when (oddp x) sum (* x x)) (collect -s um (choose-if #'oddp numbers)) Also consider: (ma y b e app rop riate sometimes) ;; Introduce read macro: (reduce #'+ numbers :key #L(if (oddp _) (* _ _) 0)) ;; Generate intermed iat e garbage: (reduce #'+ (remove #'evenp (mapcar #'square numbers)) ) 69

  58. 4.3. Control Abstraction F unctional vs. Imp erative St yle It has b een a rgued that imp erative st yle p rograms a re ha rder to reason ab out. Here is a bug that stems from an imp erative app roach: T ask: W rite a version of the built-in function find . Bad: inco rrect (defun i-find (item seq &key (test #'eql) (test-not nil) (start 0 s-flag) (end nil) (key #'identit y) (from-end nil)) (if s-flag (setq seq (subseq seq start))) (if end (setq seq (subseq seq 0 end))) ...) Problems: � T aking subsequences generates ga rbage � No app reciation of list/vecto r di�erences � Erro r if b oth sta rt and end a re given Erro r stems from the up date to seq 70

  59. 4.3. Control Abstraction Example: Simpli�cation T ask: a simpli �er fo r logical exp ressions: (simp '(and (and a b) (and (or c (or d e)) f))) ) (AND A B (OR C D E) F) Not bad, but not p erfect: (defun simp (pred) (cond ((atom pred) pred) ((eq (car pred) 'and) (cons 'and (simp-aux 'and (cdr pred)))) ((eq (car pred) 'or) (cons 'or (simp-aux 'or (cdr pred)))) (t pred))) (defun simp-aux (op preds) (cond ((null preds) nil) ((and (listp (car preds)) (eq (caar preds) op)) (append (simp-au x op (cdar preds)) (simp-au x op (cdr preds)))) (t (cons (simp (car preds)) (simp-au x op (cdr preds))))) ) 71

  60. 4.3. Control Abstraction A Program to Simplify Exp ressions Problems: � No meaningful name fo r simp-aux � No reusable pa rts � No data accesso rs � (and), (and a) not simpli�ed Better: usable to ols (defun simp-boo l (exp) "Simplif y a boolean (and/or) expressio n. " (cond ((atom exp) exp) ((member (op exp) '(and or)) (maybe-ad d (op exp) (collect-a rg s (op exp) (mapcar #'simp-b ool (args exp))))) (t exp))) (defun collect- arg s (op args) "Return the list of args, splicing in args that have the given operator , op. Useful for simplify ing exps with associat e operator s." (loop for arg in args when (starts-wi th arg op) nconc (collect- ar gs op (args arg)) else collect arg)) 72

  61. 4.3. Control Abstraction Build Reusable T o ols (defun starts-w ith (list element) "Is this a list that starts with the given element?" (and (consp list) (eql (first list) element))) (defun maybe-ad d (op args &optional (default (get-ident it y op))) "If 1 arg, return it; if 0, return the default. If there is more than 1 arg, cons op on them. Example: (maybe-ad d 'progn '((f x))) ==> (f x) Example: (maybe-ad d '* '(3 4)) ==> (* 3 4). Example: (maybe-ad d '+ '()) ==> 0, assuming 0 is defined as the identity for +." (cond ((null args) default) ((length=1 args) (first args)) (t (cons op args)))) (deftabl e identity :init '((+ 0) (* 1) (and t) (or nil) (progn nil))) 73

  62. 4.4. Syntactic Abstraction A Language fo r Simplifying T ask: A Simpli �er fo r all Exp ressions: (simplif y '(* 1 (+ x (- y y)))) ==> x (simplif y '(if (= 0 1) (f x))) ==> nil (simplif y '(and a (and (and) b))) ==> (and a b) Syntactic abstraction de�nes a new language that is app rop riate to the p roblem. This is a p roblem-o r iented (as opp osed to co de-o riented) app roach. De�ne a language fo r simpli �cation rules, then write some: (define- si mpl if ier exp-simpl if ie r ((+ x 0) ==> x) ((+ 0 x) ==> x) ((- x 0) ==> x) ((- x x) ==> 0) ((if t x y) ==> x) ((if nil x y) ==> y) ((if x y y) ==> y) ((and) ==> t) ((and x) ==> x) ((and x x) ==> x) ((and t x) ==> x) ...) 74

  63. 4.4. Syntactic Abstraction Design Y our Language Ca refully \The abilit y to change notations emp o w ers human b eings." { Scott Kim Bad: verb ose, b rittle (setq times0-ru le '( simplify (* (? e1) 0) 0 times0-r ule ) ) (setq rules (list times0-rul e ...)) � Insu�cient abstraction � Requires naming times0-rul e three times � Intro duces unneeded global va riables � Unconventional indentation Sometimes it is useful to name rules: (defrule times0-ru le (* ?x 0) ==> 0) (Although I w ouldn't recommend it in this case.) 75

  64. 4.4. Syntactic Abstraction An Interp reter fo r Simplifying No w write an interp reter (o r a compiler): (defun simplify (exp) "Simplif y expressi on by first simplifyin g componen ts ." (if (atom exp) exp (simplify -ex p (mapcar #'simplif y exp)))) (defun-m em o simplify -ex p (exp) "Simplif y expressi on using a rule, or math." ;; The expressio n is non-atomi c. (rule-ba sed -t ran sl ato r exp *simplif ica ti on- ru le s* :rule-pat te rn #'first :rule-res po nse #'third :action #'simpli fy :otherwis e #'eval-exp )) This solution is go o d b ecause: � Simpli�cati on rules a re easy to write � Control �o w is abstracted a w a y (mostly) � It is easy to verify the rules a re co rrect � The p rogram can quickly b e up and running. If the app roach is su�cient, w e're done. If the app roach is insu�cient, w e've saved time. If it is just slo w, w e can imp rove the to ols, and other uses of the to ols will b ene�t to o. 76

  65. 4.4. Syntactic Abstraction An Interp reter fo r T ranslating \Success comes from doing the same thing over and over again; each time y ou lea rn a little bit and y ou do a little b etter the next time." { Jonathan Sachs Abstract out the rule-based translato r: (defun rule-bas ed- tr ans la tor (input rules &key (matcher #'pat-mat ch ) (rule-pat ter n #'first) (rule-re spo ns e #'rest) (action #identity ) (sub #'sublis) (otherwis e #'identi ty )) "Find the first rule that matches input, and apply the action to the result of substitut in g the match result into the rule's response. If no rule matches, apply otherwis e to the input." (loop for rule in rules for result = (funcall matcher (funcall rule-patte rn rule) input) when (not (eq result fail)) do (RETURN (funcall action (funcall sub result (funcall rule-respo nse rule)))) finally (RETURN (funcall otherwise input)))) If this implem entati on is to o slo w, w e can index b etter o r compile. Sometimes, reuse is at an info rmal level: seeing ho w the general to ol is built allo ws a p rogrammer to con- struct a custom to ol with cut and paste. 77

  66. 4.4. Syntactic Abstraction Saving duplicate w o rk: defun-memo Less extreme than de�ning a whole new language is to augment the Lisp language with new macros. defun-me mo mak es a function rememb er all computa- tions it has made. It do es this b y maintaini ng a hash table of input/output pairs. If the �rst a rgument is just the function name, 1 of 2 things happ en: [1] If there is exactly 1 a rg and it is not a &rest a rg, it mak es a eql table on that a rg. [2] Otherwise, it mak es an equal table on the whole a rglist. Y ou can also replace fn-name with (name :test ... :size ... :k ey-exp ...). This mak es a table with given test and size, indexed b y k ey-exp. The hash table can b e clea red with the clea r-mem o function. Examples: (defun-m em o f (x) ;; eql table keyed on x (complex -co mp uta ti on x)) (defun-m em o (f :test #'eq) (x) ;; eq table keyed on x (complex -co mp uta ti on x)) (defun-m em o g (x y z) ;; equal table (another -co mp uta ti on x y z)) ;; keyed on on (x y . z) (defun-m em o (h :key-exp x) (x &optional debug?) ;; eql table keyed on x ...) 78

  67. 4.4. Syntactic Abstraction Saving Duplicate W o rk: defun-memo (defmacr o defun-memo (fn-name- an d-o pt ion s (&rest args) &body body) ;; Document at ion string on previous page (let ((vars (arglist- va rs args))) (flet ((gen-body (fn-name &key (test '#'equal ) size key-exp) `(eval-whe n (load eval compile) (setf (get ',fn-name 'memoize- ta bl e) (make-has h-t ab le :test ,test ,@(when size `(:size ,size)))) (defun ,fn-name ,args (gethash -or -s et -de fa ult ,key-exp (get ',fn-name 'memoize- tab le ) (progn ,@body)))) )) ;; Body of the macro: (cond ((consp fn-name-an d- opt io ns) ;; Use user-suppl ie d keywords , if any (apply #'gen-body fn-name-a nd -op ti on s)) ((and (= (length vars) 1) (not (member '&rest args))) ;; Use eql table if it seems reasonable (gen-body fn-name-a nd- op tio ns :test '#'eql :key-exp (first vars))) (t ; Otherwis e use equal table on all args (gen-body fn-name-a nd- op tio ns :test '#'equal :key-exp `(list* ,@vars))) ))) ) 79

  68. 4.4. Syntactic Abstraction Mo re Macros (defmacr o with-gensy ms (symbols body) "Replace the given symbols with gensym-e d versions , everywhe re in body. Useful for macros." ;; Does this everywhere , not just for "variable s" (sublis (mapcar #'(lambda (sym) (cons sym (gensym (string sym)))) symbols) body)) (defmacr o gethash-or -se t- def au lt (key table default) "Get the value from table, or set it to the default. Doesn't evaluate the default unless needed." (with-ge nsy ms (keyvar tabvar val found-p) `(let ((keyvar ,key) (tabvar ,table)) (multiple -va lu e-b in d (val found-p) (gethash keyvar tabvar) (if found-p val (setf (gethash keyvar tabvar) ,default)) )) )) 80

  69. 4.4. Syntactic Abstraction Use Macros App rop riately (See tuto rial b y Allan W echsler) The design of macros: � Decide if a macro is really necessa ry � Pick a clea r, consistent syntax fo r the macro � Figure out the right expansion � Use defmacro and ` to implem ent the mapping � In most cases, also p rovide a functional interface (useful, sometimes easier to alter and continue) Things to think ab out: � Don't use a macro where a function w ould su�ce � Mak e sure nothing is done at expansion time (mostly) � Evaluate a rgs left-to-right, once each (if at all) � Don't clash with user names (with-gensyms) 81

  70. 4.4. Syntactic Abstraction Problems with Macros Bad: should b e an inline function (defmacr o name-part- of (rule) `(car ,rule)) Bad: should b e a function (defmacr o defpredfun (name evaluati on -fu nc tio n) `(push (make-pre df un :name ,name :evaluatio n-f un cti on ,evaluati on -fu nc ti on) *predicat e- fun ct ion s* )) Bad: w o rks at expansion time (defmacr o defclass (name &rest def) (setf (get name 'class) def) ... (list 'quote name)) 82

  71. 4.4. Syntactic Abstraction Problems with Macros Bad: Macros should not eval a rgs (defmacr o add-person (name mother father sex unevalua te d-a ge ) (let ((age (eval unevaluat ed -a ge) )) (list (if (< age 16) ... ...) ...))) (add-per so n bob joanne jim male (compute-a ge 1953)) What if y ou compiled this call no w and loaded it in a few y ea rs? Better: Let the compiler constant-fold (declaim (inline compute- age )) (defmacr o add-person (name mother father sex age) `(funcal l (if (< ,age 16) ... ...) ...))) V ery Bad: (what if increment is n?) (defmacr o for ((variabl e start end &optional increment ) &body body) (if (not (numberp increment) ) (setf increment 1)) ...) (for (i 1 10) ...) 83

  72. 4.4. Syntactic Abstraction Macros fo r Control Structures Go o d: �lls a hole in o rthogonalit y of CL (defmacr o dovector ((var vector &key (start 0) end) &body body) "Do body with var bound to each element of vector. You can specify a subrange of the vector." `(block nil (map-vect or #'(lambda (,var) ,@body) ,vector :start start :end end))) (defun map-vect or (fn vector &key (start 0) end) "Call fn on each element of vector within a range." (loop for i from start below (or end (length vector)) do (funcall fn (aref vector-v ar index)))) � Iterates over a common data t yp e � F ollo ws established syntax ( dolist, dotimes ) � Ob eys decla rations, returns � Extends established syntax with k eyw o rds � One bad p oint: No result as in dolist , dotimes 84

  73. 4.4. Syntactic Abstraction Help er F unctions F o r Macros Most macros should expand into a call to a function. The real w o rk of the macro dovector is done b y a func- tion, map-vector b ecause: � It's easier to patch � It's sepa rately callable (useful fo r p rogram) � The resulting co de is smaller � If p refered, the help er can b e made inline (Often go o d to avoid consing closures) (dovecto r (x vect) (print x)) macro-expands to: (block nil (map-vect or #'(lambda (x) (print x)) vect :start 0 :end nil)) which inline expands to (roughly): (loop for i from 0 below (length vect) do (print (aref vect i))) 85

  74. 4.4. Syntactic Abstraction Setf Metho ds As in macros, w e need to b e sure to evaluate each fo rm exactly once, in left-to-right o rder. Mak e sure macro expansions ( macroexpan d , get-setf -me th od ) a re done in the right environment. (defmacr o deletef (item sequence &rest keys &environ men t environmen t) "Destruc tiv el y delete item from sequence ." (multipl e-v al ue- bi nd (temps vals stores store-form access-for m) (get-setf -me th od sequence environmen t) (assert (= (length stores) 1)) (let ((item-va r (gensym "ITEM"))) `(let* ((,item-va r ,item) ,@(mapcar #'list temps vals) (,(first stores) (delete ,item-var ,access-f orm ,@keys)) ) ,store-for m) ))) 86

  75. 5. Programmi ng in the La rge Programming in the La rge Be a w a re of stages of soft w a re development: � Gathering requirements � Architecture � Comp onent Design � Implementation � Debugging � T uning These can overlap. The p oint of explo rato ry p rogram- ming is to minim ize comp onent design time, getting quickly to implementati on in o rder to decide if the a r- chitecture and requirements a re right. Kno w ho w to put together a la rge p rogram: � Using pack ages � Using defsystem � Sepa rating source co de into �les � Do cumentation in the la rge � P o rtabilit y � Erro r handling � Interfacing with non-Lisp p rograms 87

  76. 5. Programmi ng in the La rge Sepa rating Source Co de into Files The follo wing facto rs a�ect ho w co de is decomp osed into �les � Language-imp osed dep endencies macros, inline functions, CLOS classes b efo re use � Strati�ed design isolate reusable comp onents � F unctional decomp osition group related comp onents � Compatibili t y with to ols chose go o d size �les fo r edito r, compile-f il e � Sepa rate OS/machine/vendo r-sp eci�c implem en- tations 88

  77. 5. Programmi ng in the La rge Using Comments E�ectively Use comments to/fo r: � Explain philosophy . Don't just do cument de- tails; also do cument philosophy , motivation, and metapho rs that p rovide a framew o rk fo r under- standing the overall structure of the co de. � O�er examples. Sometimes an example is w o rth a pile of do cumentation. � Have conversations with other develop ers! In a collab o rative p roject, y ou can sometimes ask a question just b y putting it in the source. Y ou ma y come back to �nd it answ ered. Leave the question and the answ er fo r others who might later w onder, to o. � Maintain y our \to do" list. Put a sp ecial ma rk er on comments that y ou w ant to return to later: ??? o r !!! ; ma yb e use !!!! fo r higher p rio rit y . Some p rojects k eep to do lists and change logs in �les that a re sepa rate from the source co de. (defun factoria l (n) ;; !!! What about negative numbers? --Joe 03-Aug-9 3 ;; !!! And what about non-numb ers ?? -Bill 08-Aug-9 3 (if (= n 0) 1 (* n (factoria l (- n 1))))) 89

  78. 5. Programmi ng in the La rge Do cumentation: Sa y What Y ou Mean Q: Do y ou ever use comments when y ou write co de? \Ra rely , except at the b eginning of p ro cedures, and then I only comment on the data structure. I don't comments on the co de itself b ecause I feel that p rop- erly written co de is very self-do cumented." { Ga ry Kildall \I �gure there a re t w o t yp es of comments: one is ex- plaining the obvious, and those a re w o rse than w o rth- less, the other kind is when y ou explain really involved, convoluted co de. W ell. I alw a ys try to avoid convo- luted co de. I try to p rogram really strong, clea r, clean co de, even if it mak es an extra �ve lines. I am almost of the opinion that the mo re comments y ou need, the w o rse y our p rogram is and something is wrong with it." { W a yne Ratli� \Don't comment bad co de|rewrite it." { Kernighan & Plauger � Describ e the purp ose and structure of system � Describ e each �le � Describ e each pack age � Do cumentation strings fo r all functions � Consider automatic to ols ( manual ) � Mak e co de, not comments 90

  79. 5. Programmi ng in the La rge Do cumentation: Over-comme nting These 32-lines must do cument a majo r system: ; ========= == == == == == == == == == == == == == == === == == == == == == == == == == == == == == ; ; describe ; -------- ; ; argument s : snepsul- ex p - <snepsul- ex p> ; ; returns : <node set> ; ; descript io n : This calls "sneval " to evaluate "snepsu l- ex p" to ; get the desired <node set>. ; It prints the descripti on of each <node> in the ; <node set> that has not yet been describe d during ; the process; the descript io n includes the ; descript io n of all <node>s dominate d by the <node>. ; It returns the <node set>. ; ; implemen ta ti on : Stores the <node>s which have already been describe d ; in "descri be -n od es ". ; Before tracing the descript io n of a <node>, it ; checks whether the <node> was already been describe d ; to avoid describ in g the same <node> repeate dl y. ; The variable "descri be- no de s" is updated by "des1". ; ; side-eff ec ts : Prints the <node>' s descripti on s. ; ; written: CCC 07/28/83 ; modified : CCC 09/26/83 ; ejm 10/10/83 ; njm 09/28/88 ; njm 4/27/89 91

  80. 5. Programmi ng in the La rge Do cumentation: Over-comme nting (defmacr o describe (&rest snepsul-e xp ) `(let* ((crntct (processco nt ex tde sc r ',snepsu l- exp )) (ns (in-conte xt .ns (nseval (getsndesc r ',snepsul -e xp) ) crntct)) (described -no de s (new.ns) ) (full nil)) (declare (special crntct described- no des full)) (terpri) (mapc #'(lambda (n) (if (not (ismemb. ns n described- no des )) (PP-nodet re e (des1 n)))) ns) (terpri) (values ns crntct))) Problems: � Do cumentation to o long; lose big picture � Do cumentation is wrong: describ e(d)-no des. � Do cumentation is ine�ective: no do c string � Do cumentation is redundant (a rglist) � Bad idea to shado w Lisp's describ e function � Need function that is sepa rate from macro � Abb reviations a re obscure 92

  81. 5. Programmi ng in the La rge Do cumentation: Commenting Better: This do esn't handle crntct (whatever that is) (defmacr o desc (&rest snepsul- ex p) "Describ e the node referred to by this expressi on. This macro is intended as an interacti ve debugging tool; use the function describe- no de -se t from a program. " `(descri be- no de- se t (exp->no de -se t ',snepsul- ex p)) ) (defun describe -no de -se t (node-set) "Print all the nodes in this node set." ;; Accumula te described -no de s to weed out duplicat es . (let ((descri bed -n ode s (new-node- se t)) ) (terpri) (dolist (node node-set) (unless (is-membe r- nod e- se t node describe d- nod es ) ;; des1 adds nodes to described- no des (pp-nodetr ee (des1 node described- nod es )) )) (terpri) node-set) ) 93

  82. 5. Programmi ng in the La rge P o rtabilit y Mak e y our p rogram run w ell in the environment(s) y ou use. But b e a w a re that y ou o r someone else ma y w ant to use it in another environment someda y . � Use #+ feature and #- feature � Isolate implementati on-dep endent pa rts. � Maintain one source and multipl e bina ries � Evolve to w a rds dpANS CL Implement missing features if needed � Be a w a re of vendo r-sp eci�c extensions 94

  83. 5. Programmi ng in the La rge F o reign F unction Interface La rge p rograms often have to interface with other p ro- grams written in other languages. Unfo rtunately , there is no standa rd fo r this. � Lea rn y our vendo r's fo reign interface � T ry to minimi ze exchange of data � Bew a re of a reas that cause p roblems: Memo ry management Signal handling 95

  84. 6. Miscellaneous Mean what y ou sa y � Don't mislead the reader Anticipate reader's misunderstandings � Use the right level of sp eci�cit y � Be ca reful with decla rations Inco rrect decla rations can b reak co de � One-to-one co rresp ondence Bad decla ration: only made-up example (defun lookup (name) (declare (type string name)) (if (null name) nil (or (gethash name *symbol- tab le *) (make-symb ol- en try name)))) Should b e (declare (type (or string null) name)) 96

  85. 6. Miscellaneous Naming Conventions: Be Consistent Be consistent in names: � Be consistent with capitalizati on most p refer like-thi s , not LikeThis � *special-v ar iab le * � +constant+ (o r some convention) � Dylan uses <class> � Consider structure. sl ot � -p o r ? ; ! o r n ; -> o r -to- � verb-object: delete-f ile object-attribute: integer- le ngt h compa re name-fil e and file-name don't use object-verb o r attribute-object! � Order a rguments consistently � Distinguish internal and external functions Don't mix &optional and &key ; use ca refully 1 o r 2 &optional a rgs (Dylan 0) Use k eyw o rds consistently ( key, test, end ) 97

  86. 6. Miscellaneous Naming Conventions: Cho ose Names Wisely Cho ose Names wisely: � Minimize abb reviations Most w o rds have many p ossible abb reviations but only one co rrect sp elling. Sp ell out names so they a re easier to read, rememb er, and �nd. Some p ossible exceptions: cha r, demo, intro, and pa ren. These w o rds a re b ecoming almost lik e real w o rds in English. A go o d test (fo r native English sp eak ers) is: W ould y ou sa y the w o rd aloud in conversation? Our ea rlier example with crntct and processcon te xtd es cr w ouldn't pass this test. � Don't shado w a lo cal va riable with another. � Clea rly sho w va riables that a re up dated. � Avoid ambiguous names; Use previous o r final instead of last . 98

  87. 6. Miscellaneous Notational T ricks: P a rens in Column 0 Most text edito rs treat a left pa ren in column 0 as the sta rt of a top-level exp ression. A pa ren inside a string in column 0 ma y confuse the edito r unless y ou p rovide a backslash: (defun factoria l (n) "Compute the factorial of an integer. \(don't worry about non-intege r args)." (if (= n 0) 1 (* n (factoria l (- n 1))))) Many text edito rs will treat a "(def" in column 0 as a de�nition, but not a "(def" in other columns. So y ou ma y need to do this: (progn (defun foo ...) (defun bar ...) ) 99

  88. 6. Miscellaneous Multi-Line Strings In case of a multi-l i ne string as a literal constant, such as: (defun find-sub jec t- lin e (message-h ea der -s tri ng ) (search " Subject: " message-he ade r- str in g) ) consider instead using read-time evaluation and a call to format : (defun find-sub jec t- lin e (message-h ea der -s tri ng ) (search #.(format nil "~%Subje ct: ") message-h ea der -s tri ng )) Where the same string is used many times, consider using a global va riable o r named constant: (defpara me ter *subject- ma rke r* (format nil "~%Subjec t: ")) (defun find-sub jec t- lin e (message-h ea der -s tri ng ) (search *subject-m ark er * message- he ade r- str in g) ) 100

Recommend


More recommend