This document is an extremely short overview of the Common Lisp Object System (CLOS). Newcomers to CLOS are often intimidated by its apparent complexity and possibly confused by its unusual approach. For a long time I considered CLOS to be “too hairy”, but after working on a large project that made heavy use of CLOS, I found that CLOS is actually one of the simplest and easiest object systems to understand and use. The more complex parts of CLOS can be safely ignored.
In CLOS, the objects are the least important thing! CLOS objects are barely more than simple structures. In particular, “methods” are not associated with objects. For the first part of this introduction, we'll ignore them.
CLOS is all about generic functions. A generic function is an object that you use just like a regular function, but it contains a bunch of methods that determine what happens when you invoke the function. Again, the details here are somewhat complex (because you can program how the generic function figures out which methods to use), but normally you can ignore the details. I'll start with a simple example.
The prepend
generic function will take two
arguments, an item and some sort of thing to extend. It will
return a new thing with the item prepended on the front.
Example:
(prepend #\F "oo") => "Foo" (prepend 4 '(a b c)) => (4 a b c)
The code for prepend
is as follows:
(defgeneric prepend (item thing) (:method ((item character) (thing string)) (concatenate 'string (string item) thing)) (:method (item (thing cons)) (cons item thing)))
DEFGENERIC
defines a generic function. The
function's name will be prepend
. It will take two
arguments. All methods contained in prepend
must take two arguments. The first method is for when
item is a character and thing is a string, the
second method is for when thing is a cons.
You do not have to enumerate all the methods at once. In fact,
you should probably put methods close to the relevant class
declarations. Use DEFMETHOD
to add methods to
existing generic functions.
(defmethod prepend ((item string) (thing string)) (concatenate 'string item thing)) (prepend "Foo" "Bar") => "FooBar"
When a generic function is invoked, the first thing that happens is that the applicable methods are selected. If the type of the argument matches the type specified by the method, then the method is applicable. Inapplicable methods will not participate further.
Once the applicable methods are found, they are combined. The “standard method combination” is to simply select the most specific method. So if more than one method is applicable, the one that most closely matches the actual types of the arguments is used.
(defgeneric test-which-method (object) (:method (object) "This is an object.") (:method ((object number)) "This is a number.") (:method ((object integer)) "This is an integer.")) (test-which-method 27.2) => "This is a number"
Why do they call it combination rather than selection? Because the less specific methods are not discarded, they can still be called. This is much like calling the base method in other languages.
(defgeneric test-which-method (object) (:method (object) (list "This is an object.")) (:method ((object number)) (cons "This is a number." (call-next-method))) (:method ((object integer)) (cons "This is an integer." (call-next-method)))) (test-which-method 33) => ("This is an integer." "This is a number." "This is an object.")
Let's return to objects. Objects are instances of classes. When you define a class, you specify the superclasses that this class will derive from. All methods that apply to any superclass will apply to this class as well. In addition, you supply a set of slots (fields). The class will have the fields you specify in addition to having the fields that are inherited from the superclasses. There is no subtractive inheritance --- private or protected fields --- the system trusts that you know how to behave yourself.
Slots are no good if you cannot manipulate them, so there are
slot options that you will want to specify. A typical
defclass
may look like this:
(defclass my-class (super1 super2) ((name :initarg :name :initform "No name supplied." :reader get-name) (tag :initform (gensym) :reader read-tag) (value :accessor value-of)))
The name
slot may be initialized when you create an
instance of my-class
, but since there are no
:initarg
s for the other slots, you cannot initialize
them. The :initform
specifies what value to use
should the :initarg not be supplied. The tag
slot
will be initialized by calling gensym
, the value slot
will be uninitialized.
The generic-function get-name
will be extended
with a method that will return the value of the name
slot. A setf
method will not be defined, so
name
will be a read-only slot. tag
will
be read-only as well through a call to read-tag
. The
value
slot is both readable and writable.
(setq foo (make-instance 'my-class :name "Foo")) (get-name foo) => "Foo" (value-of foo) Error: slot `value' unbound. (setf (value-of foo) 22) (value-of foo) => 22
Why aren't the readers and accessors and initargs automatically generated? There are simply too many ways to do that incorrectly --- name collisions, shadowing, package issues --- much better to give the user explicit control.
Believe it or not, the rest of CLOS is simply variations on these themes, so here ends the introduction. This is nowhere near a complete tutorial, but it should give you an idea of what CLOS is like. Should you wish to learn more, you should peruse some of the more detailed CLOS tutorials on the web or read Sonya Keene's book.
Last modified: Wed Apr 04 12:28:13 Pacific Daylight Time 2007