CLOS Tutorial

CLOS is the “Common Lisp Object System”, arguably one of the most
powerful object systems available in any language.

Some of its features include:

it is dynamic, making it a joy to work with in a Lisp REPL. For
example, changing a class definition will update the existing
objects, given certain rules which we have control upon.

it supports multiple dispatch and multiple inheritance,

it is different from most object systems in that class and method
definitions are not tied together,

it has excellent introspection capabilities,

it is provided by a meta-object protocol, which provides a
standard interface to the CLOS, and can be used to create new object
systems.

The functionality belonging to this name was added to the Common Lisp
language between the publication of Steele’s first edition of “Common
Lisp, the Language” in 1984 and the formalization of the language as
an ANSI standard ten years later.

This page aims to give a good understanding of how to use CLOS, but
only a brief introduction to the MOP.

So, our person class doesn’t explicitely inherit from another class
(it gets the empty parentheses ()). However it still inherits by default from
the class t and from standard-object. See below under
“inheritance”.

We could write a minimal class definition without slots options like this:

Getters and setters (accessor, reader, writer)

:accessor foo: an accessor is both a getter and a
setter. Its argument is a name that will become a generic
function.

(name p1) ;; => "me"
(type-of #'name)
STANDARD-GENERIC-FUNCTION

:reader and :writer do what you expect. Only the :writer is setf-able.

If you don’t specify any of these, you can still use slot-value.

You can give a slot more than one :accessor, :reader or :initarg.

We introduce two macros to make the access to slots shorter in some situations:

1- with-slots allows to abbreviate several calls to slot-value. The
first argument is a list of slot names. The second argument evaluates
to a CLOS instance. This is followed by optional declarations and an
implicit progn. Lexically during the evaluation of the body, an
access to any of these names as a variable is equivalent to accessing
the corresponding slot of the instance with slot-value.

(with-slots (name lisper)
c1
(format t "got ~a, ~a~&" name lisper))

or

(with-slots ((n name)
(l lisper))
c1
(format t "got ~a, ~a~&" n l))

2- with-accessors is equivalent, but instead of a list of slots it
takes a list of accessor functions. Any reference to the variable
inside the macro is equivalent to a call to the accessor function.

Note: this is your first introduction to the MOP. You don’t need that to get started !

The object my-point is an instance of the class named point, and the
class named point is itself an instance of the class named
standard-class. We say that the class named standard-class is
the metaclass (i.e. the class of the class) of
my-point. We can make good uses of metaclasses, as we’ll see later.

A subclass inherits all of its parents slots, and it can override any
of their slot options. Common Lisp makes this process dynamic, great
for REPL session, and we can even control parts of it (like, do
something when a given slot is removed/updated/added, etc).

We can further inspect our classes with
class-direct-[subclasses, slots, default-initargs] and many more functions.

How slots are combined follows some rules:

:accessor and :reader are combined by the union of accessors
and readers from all the inherited slots.

:initarg: the union of initialization arguments from all the
inherited slots.

:initform: we get the most specific default initial value
form, i.e. the first :initform for that slot in the precedence
list.

:allocation is not inherited. It is controlled solely by the class
being defined and defaults to :instance.

Last but not least, be warned that inheritance is fairly easy to
misuse, and multiple inheritance is multiply so, so please take a
little care. Ask yourself whether foo really wants to inherit from
bar, or whether instances of foo want a slot containing a bar. A
good general guide is that if foo and bar are “same sort of thing”
then it’s correct to mix them together by inheritance, but if they’re
really separate concepts then you should use slots to keep them apart.

Multiple inheritance

CLOS supports multiple inheritance.

(defun baby (child person)
())

The first class on the list of parent classes is the most specific
one, child’s slots will take precedence over the person’s

TODO (but
remember how slots are merged).

Redefining and changing a class

This section briefly covers two topics:

redefinition of an existing class, which you might already have done
by following our code snippets, and what we do naturally during
development, and

changing an instance of one class into an instance of another,
a powerful feature of CLOS that you’ll probably won’t use very often.

We’ll gloss over the details. Suffice it to say that everything’s
configurable by implementing methods exposed by the MOP.

To redefine a class, simply evaluate a new defclass form. This then
takes the place of the old definition, the existing class object is
updated, and all instances of the class (and, recursively, its
subclasses) are lazily updated to reflect the new definition. You don’t
have to recompile anything other than the new defclass, nor to
invalidate any of your objects. Think about it for a second: this is awesome !

print-unreadable-object prints the #<...>, that says to the reader
that this object can not be read back in. Its :type t argument asks
to print the object-type prefix, that is, PERSON. Without it, we get
#<me, lisper: T>.

We used the with-accessors macro, but of course for simple cases this is enough:

We see here that symbols are instances of the system class
symbol. This is one of 75 cases in which the language requires a
class to exist with the same name as the corresponding lisp
type. Many of these cases are concerned with CLOS itself (for
example, the correspondence between the type standard-class and
the CLOS class of that name) or with the condition system (which
might or might not be built using CLOS classes in any given
implementation). However, 33 correspondences remain relating to
“traditional” lisp types:

Note that not all “traditional” lisp types are included in this
list. (Consider: atom, fixnum, short-float, and any type not
denoted by a symbol.)

The presence of t is interesting. Just as every lisp
object is of type t, every lisp object is also a member
of the class named t. This is a simple example of
membership of more then one class at a time, and it brings into
question the issue of inheritance, which we will consider
in some detail later.

(find-class t)
;; #<BUILT-IN-CLASS T 20305AEC>

In addition to classes corresponding to lisp types, there is also a
CLOS class for every structure type you define:

The metaclass of a structure-object is the class
structure-class. It is implementation-dependent whether
the metaclass of a “traditional” lisp object is
standard-class, structure-class, or
built-in-class. Restrictions:

|built-in-class| May not use make-instance, may not use slot-value, may not use defclass to modify, may not create subclasses.|
|structure-class| May not use make-instance, might work with slot-value (implementation-dependent). Use defstruct to subclass application structure types. Consequences of modifying an existing structure-class are undefined: full recompilation may be necessary.|
|standard-class|None of these restrictions.|

Generic functions (defgeneric, defmethod)

A generic function is a lisp function which is associated
with a set of methods and dispatches them when it’s invoked. All
the methods with the same function name belong to the same generic
function.

The defmethod form is similar to a defun. It associates a body of
code with a function name, but that body may only be executed if the
types of the arguments match the pattern declared by the lambda list.

They can have optional, keyword and &rest arguments.

The defgeneric form defines the generic function. If we write a
defmethod without a corresponding defgeneric, a generic function
is automatically created (see examples).

It is generally a good idea to write the defgenerics. We can add a
default implementation and even some documentation.

The required parameters in the method’s lambda list may take one of
the following three forms:

1- a simple variable:

(defmethod greet (foo)
...)

This method can take any argument, it is always applicable.

The variable foo is bound to the corresponding argument value, as
usual.

2- a variable and a specializer, as in:

(defmethod greet ((foo person))
...)

In this case, the variable foo is bound to the corresponding
argument only if that argument is of specializer class personor a subclass,
like child (indeed, a “child” is also a “person”).

If any argument fails to match its
specializer then the method is not applicable and it cannot be
executed with those arguments.We’ll get an error message like
“there is no applicable method for the generic function xxx when
called with arguments yyy”.

Only required parameters can be specialized. We can’t specialize on optional &key arguments.

In place of a simple symbol (:soup), the eql specializer can be any
lisp form. It is evaluated at the same time of the defmethod.

You can define any number of methods with the same function name but
with different specializers, as long as the form of the lambda list is
congruent with the shape of the generic function. The system chooses
the most specific applicable method and executes its body. The most
specific method is the one whose specializers are nearest to the head
of the class-precedence-list of the argument (classes on the left of
the lambda list are more specific). A method with specializers is more
specific to one without any.

Notes:

It is an error to define a method with the same function name as
an ordinary function. If you really want to do that, use the
shadowing mechanism.

To add or remove keys or rest arguments to an existing generic
method’s lambda list, you will need to delete its declaration with
fmakunbound (or C-c C-u (slime-undefine-function) with the
cursor on the function in Slime) and start again. Otherwise,
you’ll see:

attempt to add the method
#<STANDARD-METHOD NIL (#<STANDARD-CLASS CHILD>) {1009504233}>
to the generic function
#<STANDARD-GENERIC-FUNCTION GREET (2)>;
but the method and generic function differ in whether they accept
&REST or &KEY arguments.

Methods can be redefined (exactly as for ordinary functions).

The order in which methods are defined is irrelevant, although
any classes on which they specialize must already exist.

An unspecialized argument is more or less equivalent to being
specialized on the class t. The only difference is that
all specialized arguments are implicitly taken to be “referred to” (in
the sense of declare ignore.)

Each defmethod form generates (and returns) a CLOS
instance, of class standard-method.

An eql specializer won’t work as is with strings. Indeed, strings
need equal or equalp to be compared. But, we can assign our string
to a variable and use the variable both in the eql specializer and
for the function call.

All the methods with the same function name belong to the same generic function.

All slot accessors and readers defined by defclass are methods. They can override or be overridden by other methods on the same generic function.

During the execution of a method, the remaining applicable methods
are still accessible, via the local functioncall-next-method. This function has lexical scope within
the body of a method but indefinite extent. It invokes the next most
specific method, and returns whatever value that method returned. It
can be called with either:

no arguments, in which case the next method will
receive exactly the same arguments as this method did, or

explicit arguments, in which case it is required that the
sorted set of methods applicable to the new arguments must be the same
as that computed when the generic function was first called.

Calling call-next-method when there is no next method
signals an error. You can find out whether a next method exists by
calling the local function next-method-p (which also has
has lexical scope and indefinite extent).

Note finally that the body of every method establishes a block with the same name as the method’s generic function. If you return-from that name you are exiting the current method, not the call to the enclosing generic function.

Method qualifiers (before, after, around)

In our “Diving in” examples, we saw some use of the :before, :after and :aroundqualifiers:

(defmethod foo :before (obj) (...))

(defmethod foo :after (obj) (...))

(defmethod foo :around (obj) (...))

By default, in the standard method combination framework provided by
CLOS, we can only use one of those three qualifiers, and the flow of control is as follows:

a before-method is called, well, before the applicable primary
method. If they are many before-methods, all are called. The
most specific before-method is called first (child before person).

the most specific applicable primary method (a method without
qualifiers) is called (only one).

all applicable after-methods are called. The most specific one is
called last (after-method of person, then after-method of child).

The generic function returns the value of the primary method. Any
values of the before or after methods are ignored. They are used for
their side effects.

And then we have around-methods. They are wrappers around the core
mechanism we just described. They can be useful to catch return values
or to set up an environment around the primary method (set up a catch,
a lock, timing an execution,…).

If the dispatch mechanism finds an around-method, it calls it and
returns its result. If the around-method has a call-next-method, it
calls the next most applicable around-method. It is only when we reach
the primary method that we start calling the before and after-methods.

Thus, the full dispatch mechanism for generic functions is as follows:

compute the applicable methods, and partition them into
separate lists according to their qualifier;

if there is no applicable primary method then signal an
error;

sort each of the lists into order of specificity;

execute the most specific :around method and
return whatever that returns;

if an :around method invokes
call-next-method, execute the next most specific
:around method;

if there were no :around methods in the first
place, or if an :around method invokes
call-next-method but there are no further
:around methods to call, then proceed as follows:

a. run all the :before methods, in order,
ignoring any return values and not permitting calls to
call-next-method or
next-method-p;

b. execute the most specific primary method and return
whatever that returns;

c. if a primary method invokes call-next-method,
execute the next most specific primary method;

d. if a primary method invokes call-next-method
but there are no further primary methods to call then signal an
error;

e. after the primary method(s) have completed, run all the
:after methods, in reverse
order, ignoring any return values and not permitting calls to
call-next-method or
next-method-p.

Think of it as an onion, with all the :around
methods in the outermost layer, :before and
:after methods in the middle layer, and primary methods
on the inside.

Other method combinations

The default method combination type we just saw is named standard,
but other method combination types are available, and no need to say
that you can define your own.

The built-in types are:

progn + list nconc and max or append min

You notice that these types are named after a lisp operator. Indeed,
what they do is they define a framework that combines the applicable
primary methods inside a call to the lisp operator of that name. For
example, using the progn combination type is equivalent to calling all
the primary methods one after the other:

(progn
(method-1 args)
(method-2 args)
(method-3 args))

Here, unlike the standard mechanism, all the primary methods
applicable for a given object are called, the most specific
first.

To change the combination type, we set the :method-combination
option of defgeneric and we use it as the methods’ qualifier:

Note that these operators don’t support before, after and around
methods (indeed, there is no room for them anymore). They do support
around methods, where call-next-method is allowed, but they don’t
support calling call-next-method in the primary methods (it would
indeed be redundant since all primary methods are called, or clunky to
not call one).

CLOS allows us to define a new operator as a method combination type, be
it a lisp function, macro or special form. We’ll let you refer to the
books if you feel the need.

Debugging: tracing method combination

It is possible to trace the method
combination, but this is implementation dependent.

MOP

We gather here some examples that make use of the framework provided
by the meta-object protocol, the configurable object system that rules
Lisp’s object system. We touch advanced concepts so, new reader, don’t
worry: you don’t need to understand this section to start using the
Common Lisp Object System.

We won’t explain much about the MOP here, but hopefully sufficiently
to make you see its possibilities or to help you understand how some
CL libraries are built. We invite you to read the books referenced in
the introduction.

Metaclasses

Metaclasses are needed to control the behaviour of other classes.

As announced, we won’t talk much. See also Wikipedia for metaclasses or CLOS.

The standard metaclass is standard-class:

(class-of p1) ;; #<STANDARD-CLASS PERSON>

But we’ll change it to one of our own, so that we’ll be able to
count the creation of instances. This same mechanism could be used
to auto increment the primary key of a database system (this is
how the Postmodern or Mito libraries do), to log the creation of objects,
etc.

Controlling the initialization of instances (initialize-instance)

To further customize the creation of instances by specializing
initialize-instance, which is called by make-instance, just after
it has created a new instance but didn’t initialize it yet with the
default initargs and initforms.

It is recommended (Keene) to create an after method, since creating a
primary method would prevent slots’ initialization.

Another rational. The CLOS implementation of
make-instance is in two stages: allocate the new object,
and then pass it along with all the make-instance keyword
arguments, to the generic function
initialize-instance. Implementors and application writers
define :after methods on
initialize-instance, to initialize the slots of the
instance. The system-supplied primary method does this with regard to
(a) :initform and :initarg values supplied
with the class was defined and (b) the keywords passed through from
make-instance. Other methods can extend this behaviour as
they see fit. For example, they might accept an additional keyword
which invokes a database access to fill certain slots. The lambda list
for initialize-instance is: