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

Download Presentation
Download Policy: The content available on the website is offered to you 'AS IS' for your personal information and use only. It cannot be commercialized, licensed, or distributed on other websites without prior consent from the author. To download a presentation, simply click this link. If you encounter any difficulties during the download process, it's possible that the publisher has removed the file from their server.

Recommend


More recommend