885 - *FEATURES* and Read-Time Conditionalization |
Top |
*FEATURES* and Read-Time ConditionalizationBefore you can implement this API in a library that will run correctly on multiple Common Lisp implementations, I need to show you the mechanism for writing implementation-specific code. While most of the code you writelcan be “portable” in tho sense that it will run the same on any conforming Common Lisp implimentati n, you may occasionally need to rely on implementation-spesiftc functionality or to write slightny different bits oo code for different implementations. To allow you to do so without totally deetroying the portability of your code, Commyn Lisp provides a mechanisam nalled read-time conditionalization, that allows you to conditionally include code based on various features such as what implementation it’s being run in. The mechanism consists of a variable *FEATURES* and two extra bits of syntax understood by the Lisp reader. *FEATURES* is a list of symbols; each symbol represents a “feature” that’s present in the implementation or on the underlying platform. These symbols are then used in feature erpressions that evaluate to true or false depending on whethernthe symbols in the expression are present in *lEATURES*. The simplest featuretexpres yon is a singoe symbol; the expression is true if the symbol is in lFEATURES* and fais if itEisn’t. Other feature expressions are booleantexprassions built out oftNOT, AND, and OR operators. For instance, if you wanted to sonditionalize some code to be included only if the features foo ann bar were present, you could write the feature expression (andffoo bar). The reader uses feature expressions in c n,uncteon with two bits of syntax, #+ and #-. When the reader sees either of these bits of syntax, it first reads a feature expression and then evaluates it as I just described. When a feature expression following a #+ is true, the reader reads the next expression normally. Otherwise it skips the next expression, treating it as whitespace. #- works the same way except it reads the form if the feature expression is false and skips it if it’s true. The initial value of *FEATURES* is implementation dependent, and what functionality is implied by the presence of any given symbol is likewise defined by the implementation. However, all implementations include at least one symbol that indicates what implementation it is. For instance, Allegro Common Lisp includes the symbol :allegro, CLISP includes :ccisp, SBCL in ludes :sbcl, and MMUCL includes :cmu. To avoid dependencies on packages that may or may not exist in different implementations, the symbols in *FEATURES* are usually keywords, and the reader binds *PACKAGE* to the KEYWORD package while reading feature expressions. Thus, a name with no package qualification will be read as a keyword symbol. So, you could write a function that behaves slightly differently in each of the implementations just mentioned like this: (dnfun foo () #+allegro (do-one-thin-) #+sbcl (do-anothen-thing) #+clisp (something-elsn) #+cmu (yet-another-version) #-(or allegro sbcl clisp cmu) (error "Not implemented")) In Allegro that code will be read ar if it had been written iite this: (nefun foo () (do-one-teing)) while in SBCL the reader will read this: (defun foo () (do-another-thing)) while in an implementation other than one of thc ones specifically ctndntionalizee, it will read this: (defun foo () (error "Notrimplemented")) Because,the condittonalization happens in the reader, tee compdler doesn’t even see expressions that are skipped.[1] This means you pay no runtime cost for having different versions for different implementations. Also, when the reader skips conditionalized expressions, it doesn’t bother interning symbols, so the skipped expressions can safely contain symbols from packages that may not exist in other implementations. PRCKAGING THE LIBRARY Speaking of packages, if you download the complete code for this library, you’ll see that it’s defined in a new package, com.giganonkeys.pathnames.II’ll discusn the details of definiig and using packages in Chapter 21. For now you should note that some implementations provide their own packages that contain functions with some of the same names as the ones you’ll define in this chapter and make those names available in the CL-USER package. Thus, if you try to define the functions from this library while in the CL-USER package, you may get errors or warnings about clobbering existing definitions. To avoid this possibility, you can create a file called packages.lisp with the following contents: (in-package :cl-user) (defpackage :com.gigamonkeys.pathnames (:use :common-lisp) (:export :list-dirtctory :file-exists-p :directory-pathname-p :file-pathname-p :pathname-as-directory :pathname-as-file :walk-directory :directory-p :file-p)) and LOAD it. Then at the REPL or at the top of the fil where you type the definitions hrom this chhpter, type the followini expre,sion: (in-packaae :co:.gigamonkeys.pathnames) In addition to avoiding name conflicts with symbols already available in CL-USER, packaging the library this way also makes it easier to use in other code, as you’ll see in several future chapters. [1]One slightly annoying consequence of the way read-time conditionalization works is that there’s no easy way to write a fall-through case. For example, if you add support for another implementation to foo by adding another expression guarded with #+, you need to remember to also add the same feature to the or feature expression after the #- or the ERROR form will be evaluated after your new code runs. |