Most Common Lisp implementations have a single global environment, and it is spread out in various parts of the system. For example, the definition of a named function may be contained in a slot of the symbol representing the name, whereas the definition of a type might be in a hash table that is the value of a special variable.
In the paper First-class Global Environments in Common Lisp, presented at the European Lisp Symposium in 2015, we outlined a protocol for an alternative way of representing global environments, namely as first-class instances of a standard classes. This library is a concrete implementation of the ideas in that paper, though improved upon since.
We provide protocols and default implementations for the three kinds of global environments mentioned in the Common Lisp standard, namely the run-time environment, the evaluation environment and the compilation environment.
The use of a client parameter for our protocol generic functions allows client code to customize the functions defined here, either by extending them or by overriding them. Similarly, the classes defined here are designed to be possible to use as superclasses of client-specific environment classes.
The Clostrum API is divided into two parts, a "high level" package clostrum
, and a "low level" package clostrum-sys
. clostrum-sys
has generic functions that access environments directly and simply, without error checking, while clostrum
's generic functions mimic standard Common Lisp operators' more complex behavior and account for inheritance. clostrum
has default methods implemented in terms of clostrum-sys
, so environment implementation need only specialize the clostrum-sys
functions in order to make clostrum
's high level facilities available. Users (rather than implementors) of the Clostrum protocol should only need to use the clostrum
package.
For operating on run-time environments, the clostrum
package makes available environment manipulation functions and accessors similar to those in the standard: fdefinition
, fboundp
, fmakunbound
, macro-function
, special-operator-p
for functions; symbol-value
, boundp
, makunbound
, symbol-plist
for variables; find-class
for classes; find-package
for packages; and macroexpand-1
, macroexpand
, and constantp
.
There are additional functions and accessors corresponding to some other Lisp operators. setf-expander
can be used for define-setf-expander
; make-variable
, make-parameter
, make-constant
, and make-symbol-macro
for defvar
, defparameter
, defconstant
, and define-symbol-macro
respectively; type-expander
for deftype
, along with type-expand-1
and type-expand
to work with type specifiers.
For proclamations, proclamation
can be used for declaration
or CLtL2's define-declaration
. variable-type
covers type
, and special
proclamations can be done with make-variable
. operator-ftype
handles ftype
and operator-inline
inline
. User-defined inlining data, such as a definition, can be associated with an operator through operator-inline-data
.
note-function
will declare an operator to be a function without needing a definition for said function. This is useful for the compile-time effect of defun
.
The package environment can be accessed via find-package
. New nicknames for a package can be installed via (setf find-package)
, and the list of all names for a package in an environment retrieved via package-names
. All packages in an environment can be iterated over with map-all-packages
. Additionally, the canonical name of a package is considered part of the environment as well, and can be accessed via package-name
.
Compilation environments support a subset of the runtime environment operations. They lack package mappings, and functions only needed at runtime like (setf symbol-value)
are not implemented. macro-function
and make-constant
still work.
Each variable, operator, and type has a cell retrievable by name, and variables and operators have a status. The cell is an object of implementation-defined nature, accessed by -value
, -boundp
, and -makunbound
functions, that holds the value of a binding. Cells are an essential part of Clostrum's design and explained more in the paper. The status indicates what kind of binding a variable or operator has, e.g. as a variable or a constant, or a function or a macro. Some statuses can be read but not directly written (in the high-level API).
In the high level API, cells are retrieved by ensure-variable-cell
, etc. This will return a cell if it exists or make a new one to return if not. Undefining a variable etc. does not remove the cell, so they can be used for continued access regardless of boundedness.
The clostrum-sys
package contains the functions that must be implemented by environment objects for the high level functions to work. These consist of direct access to cells and statuses, along with additional functions for the non-cell properties such as a compiler macro functions. Additionally, a parent
function should be supported for inheritance, but the rest of the functions do not need to consult parent environments as this is handled by the high-level API.
The Common Lisp standard mentions environments inheriting from each other in the context of compilation (CLHS 3.2.1) but does not describe the semantics in detail. Clostrum supports inherited environments, and the parent of an environment can be read by parent
; if an environment has no parent, parent
returns nil
.
The basic rule of inherited bindings Clostrum has adopted is as follows: The values in bindings are inherited from parent environments, but the bindings themselves are not. Mutating a binding or making it unbound has no effect on a parent environment; in fact, no operation on a child environment will mutate anything in the parent environment.
Since the standard only contemplates environment inheritance in the context of compilation, these inheritance semantics are designed to allow compile-file
operations to mutate an evaluation environment without mutating anything in the parent. For example, a function definition or redefinition within (eval-when (:compile-toplevel) ...)
can affect an evaluation environment, but after compile-file
returns, this evaluation environment is discarded, and the startup environment has no trace.
The clostrum-basic
system is the reference implementation of Clostrum. It implements the low level interface, so the high level interface can be used with the basic environments. The classes run-time-environment
and compilation-environment
represent run-time and compilation environments respectively.
The clostrum-trucler
system implements the Trucler protocol for Clostrum environments. The system package has no exports, instead just specializing the Trucler protocol methods.