Because the invention of Lisp predated the rise of object-oriented programming by a couple decades,[170] new Lispers are sometimes surprised to discover what a thoroughly object-oriented language Common Lisp is. Common Lisp's immediate predecessors were developed at a time when object orientation was an exciting new idea and there were many experiments with ways to incorporate the ideas of object orientation, especially as manifested in Smalltalk, into Lisp. As part of the Common Lisp standardization, a synthesis of several of these experiments emerged under the name Common Lisp Object System, or CLOS. The ANSI standard incorporated CLOS into the language, so it no longer really makes sense to speak of CLOS as a separate entity.
The features CLOS contributed to Common Lisp range from those that can hardly be avoided to relatively esoteric manifestations of Lisp's language-as-language-building-tool philosophy. Complete coverage of all these features is beyond the scope of this book, but in this chapter and the next I'll describe the bread-and-butter features and give an overview of Common Lisp's approach to objects.
You should note at the outset that Common Lisp's object system offers a fairly different embodiment of the principles of object orientation than many other languages. If you have a deep understanding of the fundamental ideas behind object orientation, you'll likely appreciate the particularly powerful and general way Common Lisp manifests those ideas. On the other hand, if your experience with object orientation has been largely with a single language, you may find Common Lisp's approach somewhat foreign; you should try to avoid assuming that there's only one way for a language to support object orientation.[171] If you have little object-oriented programming experience, you should have no trouble understanding the explanations here, though it may help to ignore the occasional comparisons to the way other languages do things.
The fundamental idea of object orientation is that a powerful way to organize a program is to define data types and then associate operations with those data types. In particular, you want to be able to invoke an operation and have the exact behavior determined by the type of the object or objects on which the operation was invoked. The classic example used, seemingly by all introductions to object orientation, is an operation draw
that can be applied to objects representing various geometric shapes. Different implementations of the draw
operation can be provided for drawing circles, triangles, and squares, and a call to draw
will actually result in drawing a circle, triangle, or square, depending on the type of the object to which the draw
operation is applied. The different implementations of draw
are defined separately, and new versions can be defined that draw other shapes without having to change the code of either the caller or any of the other draw
implementations. This feature of object orientation goes by the fancy Greek name
Common Lisp, like most object-oriented languages today, is class-based; all objects are NUMBER
and STRING
have opaque representations accessible only via the standard functions for manipulating those types, while instances of user-defined classes, as you'll see in the next chapter, consist of named parts called
Classes are arranged in a hierarchy, a taxonomy for all objects. A class can be defined as a T
, which is a direct or indirect superclass of every other class. Thus, every datum in Common Lisp is an instance of T
. [173] Common Lisp also supports
Outside the Lisp family, almost all object-oriented languages follow the basic pattern established by Simula of having behavior associated with classes through
Early Lisp object systems worked in a similar way, providing a special function SEND
that could be used to send a message to a particular object. However, this wasn't entirely satisfactory, as it made method invocations different from normal function calls. Syntactically method invocations were written like this:
(send object 'foo)
rather than like this:
(foo object)
More significantly, because methods weren't functions, they couldn't be passed as arguments to higher-order functions such as MAPCAR
; if one wanted to call a method on all the elements of a list with MAPCAR
, one had to write this:
(mapcar #'(lambda (object) (send object 'foo)) objects)
rather than this:
(mapcar #'foo objects)
Eventually the folks working on Lisp object systems unified methods with functions by creating a new kind of function called a
Generic functions are the heart of Common Lisp's object system and the topic of the rest of this chapter. While I can't talk about generic functions without some mention of classes, for now I'll focus on how to define and use generic functions. In the next chapter I'll show you how to define your own classes.
A generic function defines an abstract operation, specifying its name and a parameter list but no implementation. Here, for example, is how you might define a generic function, draw
, that will be used to draw different kinds of shapes on the screen:
(defgeneric draw (shape)
(:documentation 'Draw the given shape on the screen.'))
I'll discuss the syntax of DEFGENERIC
in the next section; for now just note that this definition doesn't contain any actual code.
A generic function is generic in the sense that it can—at least in theory—accept any objects as arguments.[174] However, by itself a generic function can't actually do anything; if you just define a generic function, no matter what arguments you call it with, it will signal an error. The actual implementation of a generic function is provided by