Книга: Practical Common Lisp

Defining Your Own Packages

Defining Your Own Packages

Working in COMMON-LISP-USER is fine for experiments at the REPL, but once you start writing actual programs you'll want to define new packages so different programs loaded into the same Lisp environment don't stomp on each other's names. And when you write libraries that you intend to use in different contexts, you'll want to define separate packages and then export the symbols that make up the libraries' public APIs.

However, before you start defining packages, it's important to understand one thing about what packages do not do. Packages don't provide direct control over who can call what function or access what variable. They provide you with basic control over namespaces by controlling how the reader translates textual names into symbol objects, but it isn't until later, in the evaluator, that the symbol is interpreted as the name of a function or variable or whatever else. Thus, it doesn't make sense to talk about exporting a function or a variable from a package. You can export symbols to make certain names easier to refer to, but the package system doesn't allow you to restrict how those names are used.[224]

With that in mind, you can start looking at how to define packages and tie them together. You define new packages with the macro DEFPACKAGE, which allows you to not only create the package but to specify what packages it uses, what symbols it exports, and what symbols it imports from other packages and to resolve conflicts by creating shadowing symbols.[225]

I'll describe the various options in terms of how you might use packages while writing a program that organizes e-mail messages into a searchable database. The program is purely hypothetical, as are the libraries I'll refer to—the point is to look at how the packages used in such a program might be structured.

The first package you'd need is one to provide a namespace for the application—you want to be able to name your functions, variables, and so on, without having to worry about name collisions with unrelated code. So you'd define a new package with DEFPACKAGE.

If the application is simple enough to be written with no libraries beyond the facilities provided by the language itself, you could define a simple package like this:

(defpackage :com.gigamonkeys.email-db
(:use :common-lisp))

This defines a package, named COM.GIGAMONKEYS.EMAIL-DB, that inherits all the symbols exported by the COMMON-LISP package.[226]

You actually have several choices of how to represent the names of packages and, as you'll see, the names of symbols in a DEFPACKAGE. Packages and symbols are named with strings. However, in a DEFPACKAGE form, you can specify the names of packages and symbols with string designators. A string designator is either a string, which designates itself; a symbol, which designates its name; or a character, which designates a one-character string containing just the character. Using keyword symbols, as in the previous DEFPACKAGE, is a common style that allows you to write the names in lowercase—the reader will convert the names to uppercase for you. You could also write the DEFPACKAGE with strings, but then you have to write them in all uppercase, because the true names of most symbols and packages are in fact uppercase because of the case conversion performed by the reader.[227]

(defpackage "COM.GIGAMONKEYS.EMAIL-DB"
(:use "COMMON-LISP"))

You could also use nonkeyword symbols—the names in DEFPACKAGE aren't evaluated—but then the very act of reading the DEFPACKAGE form would cause those symbols to be interned in the current package, which at the very least will pollute that namespace and may also cause problems later if you try to use the package.[228]

To read code in this package, you need to make it the current package with the IN-PACKAGE macro:

(in-package :com.gigamonkeys.email-db)

If you type this expression at the REPL, it will change the value of *PACKAGE*, affecting how the REPL reads subsequent expressions, until you change it with another call to IN-PACKAGE. Similarly, if you include an IN-PACKAGE in a file that's loaded with LOAD or compiled with COMPILE-FILE, it will change the package, affecting the way subsequent expressions in the file are read.[229]

With the current package set to the COM.GIGAMONKEYS.EMAIL-DB package, other than names inherited from the COMMON-LISP package, you can use any name you want for whatever purpose you want. Thus, you could define a new hello-world function that could coexist with the hello-world function previously defined in COMMON-LISP-USER. Here's the behavior of the existing function:

CL-USER> (hello-world)
hello, world
NIL

Now you can switch to the new package using IN-PACKAGE.[230] Notice how the prompt changes—the exact form is determined by the development environment, but in SLIME the default prompt consists of an abbreviated version of the package name.

CL-USER> (in-package :com.gigamonkeys.email-db)
#<The COM.GIGAMONKEYS.EMAIL-DB package>
EMAIL-DB>

You can define a new hello-world in this package:

EMAIL-DB> (defun hello-world () (format t "hello from EMAIL-DB package~%"))
HELLO-WORLD

And test it, like this:

EMAIL-DB> (hello-world)
hello from EMAIL-DB package
NIL

Now switch back to CL-USER.

EMAIL-DB> (in-package :cl-user)
#<The COMMON-LISP-USER package>
CL-USER>

And the old function is undisturbed.

CL-USER> (hello-world)
hello, world
NIL

Оглавление книги


Генерация: 1.083. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз