2023-10-25 11:23:21 +02:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta name = "generator" content =
"HTML Tidy for HTML5 for Linux version 5.2.0">
< title > Fundamentals of CLOS< / title >
< meta charset = "utf-8" >
< meta name = "description" content = "A collection of examples of using Common Lisp" >
< meta name = "viewport" content =
"width=device-width, initial-scale=1">
< link rel = "icon" href =
"assets/cl-logo-blue.png"/>
< link rel = "stylesheet" href =
"assets/style.css">
< script type = "text/javascript" src =
"assets/highlight-lisp.js">
< / script >
< script type = "text/javascript" src =
"assets/jquery-3.2.1.min.js">
< / script >
< script type = "text/javascript" src =
"assets/jquery.toc/jquery.toc.min.js">
< / script >
< script type = "text/javascript" src =
"assets/toggle-toc.js">
< / script >
< link rel = "stylesheet" href =
"assets/github.css">
< link rel = "stylesheet" href = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity = "sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin = "anonymous" >
< / head >
< body >
< h1 id = "title-xs" > < a href = "index.html" > The Common Lisp Cookbook< / a > – Fundamentals of CLOS< / h1 >
< div id = "logo-container" >
< a href = "index.html" >
< img id = "logo" src = "assets/cl-logo-blue.png" / >
< / a >
< div id = "searchform-container" >
< form onsubmit = "duckSearch()" action = "javascript:void(0)" >
< input id = "searchField" type = "text" value = "" placeholder = "Search..." >
< / form >
< / div >
< div id = "toc-container" class = "toc-close" >
< div id = "toc-title" > Table of Contents< / div >
< ul id = "toc" class = "list-unstyled" > < / ul >
< / div >
< / div >
< div id = "content-container" >
< h1 id = "title-non-xs" > < a href = "index.html" > The Common Lisp Cookbook< / a > – Fundamentals of CLOS< / h1 >
<!-- Announcement we can keep for 1 month or more. I remove it and re - add it from time to time. -->
< p class = "announce" >
2024-01-12 09:23:31 +01:00
📢 🤶 ⭐
< a style = "font-size: 120%" href = "https://www.udemy.com/course/common-lisp-programming/?couponCode=LISPY-XMAS2023" title = "This course is under a paywall on the Udemy platform. Several videos are freely available so you can judge before diving in. vindarel is (I am) the main contributor to this Cookbook." > Discover our contributor's Lisp course with this Christmas coupon.< / a >
< strong >
Recently added: 18 videos on MACROS.
< / strong >
2023-10-25 11:23:21 +02:00
< a style = "font-size: 90%" href = "https://github.com/vindarel/common-lisp-course-in-videos/" > Learn more< / a > .
< / p >
< p class = "announce-neutral" >
📕 < a href = "index.html#download-in-epub" > Get the EPUB and PDF< / a >
< / p >
< div id = "content"
< p > CLOS is the “Common Lisp Object System”, arguably one of the most
powerful object systems available in any language.< / p >
< p > Some of its features include:< / p >
< ul >
< li > it is < strong > dynamic< / strong > , 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.< / li >
< li > it supports < strong > multiple dispatch< / strong > and < strong > multiple inheritance< / strong > ,< / li >
< li > it is different from most object systems in that class and method
definitions are not tied together,< / li >
< li > it has excellent < strong > introspection< / strong > capabilities,< / li >
< li > it is provided by a < strong > meta-object protocol< / strong > , which provides a
standard interface to the CLOS, and can be used to create new object
systems.< / li >
< / ul >
< p > 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.< / p >
< p > This page aims to give a good understanding of how to use CLOS, but
only a brief introduction to the MOP.< / p >
< p > To learn the subjects in depth, you will need two books:< / p >
< ul >
< li > < a href = "http://www.communitypicks.com/r/lisp/s/17592186046723-object-oriented-programming-in-common-lisp-a-programmer" > Object-Oriented Programming in Common Lisp: a Programmer’ s Guide to CLOS< / a > , by Sonya Keene,< / li >
< li > < a href = "http://www.communitypicks.com/r/lisp/s/17592186045709-the-art-of-the-metaobject-protocol" > the Art of the Metaobject Protocol< / a > , by Gregor Kiczales, Jim des Rivières et al.< / li >
< / ul >
< p > But see also< / p >
< ul >
< li > the introduction in < a href = "http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html" > Practical Common Lisp< / a > (online), by Peter Seibel.< / li >
< li > < a href = "https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node260.html#SECTION003200000000000000000" > Common Lisp, the Language< / a > < / li >
< li > and for reference, the complete < a href = "https://clos-mop.hexstreamsoft.com/" > CLOS-MOP specifications< / a > .< / li >
< / ul >
< h2 id = "classes-and-instances" > Classes and instances< / h2 >
< h3 id = "diving-in" > Diving in< / h3 >
< p > Let’ s dive in with an example showing class definition, creation of
objects, slot access, methods specialized for a given class, and
inheritance.< / p >
< pre > < code class = "language-lisp" > (defclass person ()
((name
:initarg :name
:accessor name)
(lisper
:initform nil
:accessor lisper)))
;; => #< STANDARD-CLASS PERSON>
(defvar p1 (make-instance 'person :name "me" ))
;; ^^^^ initarg
;; => #< PERSON {1006234593}>
(name p1)
;;^^^ accessor
;; => "me"
(lisper p1)
;; => nil
;; ^^ initform (slot unbound by default)
(setf (lisper p1) t)
(defclass child (person)
())
(defclass child (person)
((can-walk-p
:accessor can-walk-p
:initform t)))
;; #< STANDARD-CLASS CHILD>
(can-walk-p (make-instance 'child))
;; T
< / code > < / pre >
< h3 id = "defining-classes-defclass" > Defining classes (defclass)< / h3 >
< p > The macro used for defining new data types in CLOS is < code > defclass< / code > .< / p >
< p > We used it like this:< / p >
< pre > < code class = "language-lisp" > (defclass person ()
((name
:initarg :name
:accessor name)
(lisper
:initform nil
:accessor lisper)))
< / code > < / pre >
< p > This gives us a CLOS type (or class) called < code > person< / code > and two slots,
named < code > name< / code > and < code > lisper< / code > .< / p >
< pre > < code class = "language-lisp" > (class-of p1)
#< STANDARD-CLASS PERSON>
(type-of p1)
PERSON
< / code > < / pre >
< p > The general form of < code > defclass< / code > is:< / p >
< pre > < code > (defclass < class-name> (list of super classes)
((slot-1
:slot-option slot-argument)
(slot-2, etc))
(:optional-class-option
:another-optional-class-option))
< / code > < / pre >
< p > So, our < code > person< / code > class doesn’ t explicitly inherit from another class
(it gets the empty parentheses < code > ()< / code > ). However it still inherits by default from
the class < code > t< / code > and from < code > standard-object< / code > . See below under
“inheritance”.< / p >
< p > We could write a minimal class definition without slot options like this:< / p >
< pre > < code class = "language-lisp" > (defclass point ()
(x y z))
< / code > < / pre >
< p > or even without slot specifiers: < code > (defclass point () ())< / code > .< / p >
< h3 id = "creating-objects-make-instance" > Creating objects (make-instance)< / h3 >
< p > We create instances of a class with < code > make-instance< / code > :< / p >
< pre > < code class = "language-lisp" > (defvar p1 (make-instance 'person :name "me" ))
< / code > < / pre >
< p > It is generally good practice to define a constructor:< / p >
< pre > < code class = "language-lisp" > (defun make-person (name & key lisper)
(make-instance 'person :name name :lisper lisper))
< / code > < / pre >
< p > This has the direct advantage that you can control the required
arguments. You should now export the constructor from your package and
not the class itself.< / p >
< h3 id = "slots" > Slots< / h3 >
< h4 id = "a-function-that-always-works-slot-value" > A function that always works (slot-value)< / h4 >
< p > The function to access any slot anytime is < code > (slot-value < object> < slot-name> )< / code > .< / p >
< p > Given our < code > point< / code > class above, which didn’ t define any slot accessors:< / p >
< pre > < code class = "language-lisp" > (defvar pt (make-instance 'point))
(inspect pt)
The object is a STANDARD-OBJECT of type POINT.
0. X: "unbound"
1. Y: "unbound"
2. Z: "unbound"
< / code > < / pre >
< p > We got an object of type < code > POINT< / code > , but < strong > slots are unbound by
default< / strong > : trying to access them will raise an < code > UNBOUND-SLOT< / code >
condition:< / p >
< pre > < code class = "language-lisp" > (slot-value pt 'x) ;; => condition: the slot is unbound
< / code > < / pre >
< p > < code > slot-value< / code > is < code > setf< / code > -able:< / p >
< pre > < code class = "language-lisp" > (setf (slot-value pt 'x) 1)
(slot-value pt 'x) ;; => 1
< / code > < / pre >
< h4 id = "initial-and-default-values-initarg-initform" > Initial and default values (initarg, initform)< / h4 >
< ul >
< li > < code > :initarg :foo< / code > is the keyword we can pass to < code > make-instance< / code > to
give a value to this slot:< / li >
< / ul >
< pre > < code class = "language-lisp" > (make-instance 'person :name "me")
< / code > < / pre >
< p > (again: slots are unbound by default)< / p >
< ul >
< li > < code > :initform < val> < / code > is the < em > default value< / em > in case we didn’ t specify
an initarg. This form is evaluated each time it’ s needed, in the
lexical environment of the < code > defclass< / code > .< / li >
< / ul >
< p > Sometimes we see the following trick to clearly require a slot:< / p >
< pre > < code class = "language-lisp" > (defclass foo ()
((a
:initarg :a
:initform (error "you didn't supply an initial value for slot a"))))
;; #< STANDARD-CLASS FOO>
(make-instance 'foo) ;; => enters the debugger.
< / code > < / pre >
< h4 id = "getters-and-setters-accessor-reader-writer" > Getters and setters (accessor, reader, writer)< / h4 >
< ul >
< li > < code > :accessor foo< / code > : an accessor is both a < strong > getter< / strong > and a
< strong > setter< / strong > . Its argument is a name that will become a < strong > generic
function< / strong > .< / li >
< / ul >
< pre > < code class = "language-lisp" > (name p1) ;; => "me"
(type-of #'name)
STANDARD-GENERIC-FUNCTION
< / code > < / pre >
< ul >
< li > < code > :reader< / code > and < code > :writer< / code > do what you expect. Only the < code > :writer< / code > is < code > setf< / code > -able.< / li >
< / ul >
< p > If you don’ t specify any of these, you can still use < code > slot-value< / code > .< / p >
< p > You can give a slot more than one < code > :accessor< / code > , < code > :reader< / code > or < code > :initarg< / code > .< / p >
< p > We introduce two macros to make the access to slots shorter in some situations:< / p >
< p > 1- < code > with-slots< / code > 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 < code > progn< / code > . 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 < code > slot-value< / code > .< / p >
< pre > < code class = "language-lisp" > (with-slots (name lisper)
c1
(format t "got ~a, ~a~& " name lisper))
< / code > < / pre >
< p > or< / p >
< pre > < code class = "language-lisp" > (with-slots ((n name)
(l lisper))
c1
(format t "got ~a, ~a~& " n l))
< / code > < / pre >
< p > 2- < code > with-accessors< / code > 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.< / p >
< pre > < code class = "language-lisp" > (with-accessors ((name name)
^^variable ^^accessor
(lisper lisper))
p1
(format t "name: ~a, lisper: ~a" name lisper))
< / code > < / pre >
< h4 id = "class-vs-instance-slots" > Class VS instance slots< / h4 >
< p > < code > :allocation< / code > specifies whether this slot is < em > local< / em > or < em > shared< / em > .< / p >
< ul >
< li >
< p > a slot is < em > local< / em > by default, that means it can be different for each instance of the class. In that case < code > :allocation< / code > equals < code > :instance< / code > .< / p >
< / li >
< li >
< p > a < em > shared< / em > slot will always be equal for all instances of the
class. We set it with < code > :allocation :class< / code > .< / p >
< / li >
< / ul >
< p > In the following example, note how changing the value of the class
slot < code > species< / code > of < code > p2< / code > affects all instances of the
class (whether or not those instances exist yet).< / p >
< pre > < code class = "language-lisp" > (defclass person ()
((name :initarg :name :accessor name)
(species
:initform 'homo-sapiens
:accessor species
:allocation :class)))
;; Note that the slot "lisper" was removed in existing instances.
(inspect p1)
;; The object is a STANDARD-OBJECT of type PERSON.
;; 0. NAME: "me"
;; 1. SPECIES: HOMO-SAPIENS
;; > q
(defvar p2 (make-instance 'person))
(species p1)
(species p2)
;; HOMO-SAPIENS
(setf (species p2) 'homo-numericus)
;; HOMO-NUMERICUS
(species p1)
;; HOMO-NUMERICUS
(species (make-instance 'person))
;; HOMO-NUMERICUS
(let ((temp (make-instance 'person)))
(setf (species temp) 'homo-lisper))
;; HOMO-LISPER
(species (make-instance 'person))
;; HOMO-LISPER
< / code > < / pre >
< h4 id = "slot-documentation" > Slot documentation< / h4 >
< p > Each slot accepts one < code > :documentation< / code > option. To obtain its documentation via < code > documentation< / code > , you need to obtain the slot object. This can be done compatibly using a library such as < a href = "https://github.com/pcostanza/closer-mop" > closer-mop< / a > . For instance:< / p >
2024-01-12 09:23:31 +01:00
< pre > < code class = "language-lisp" > (closer-mop:class-direct-slots (find-class 'my-class))
;; => list of slots (objects)
(find 'my-slot * :key #'closer-mop:slot-definition-name)
;; => find desired slot by name
2023-10-25 11:23:21 +02:00
(documentation * t) ; obtain its documentation
< / code > < / pre >
< p > Note however that generally it may be better to document slot accessors instead, as a popular viewpoint is that slots are implementation details and not part of the public interface.< / p >
< h4 id = "slot-type" > Slot type< / h4 >
< p > The < code > :type< / code > slot option may not do the job you expect it does. If you
are new to the CLOS, we suggest you skip this section and use your own
constructors to manually check slot types.< / p >
< p > Indeed, whether slot types are being checked or not is undefined. See the < a href = "http://www.lispworks.com/documentation/HyperSpec/Body/m_defcla.htm#defclass" > Hyperspec< / a > .< / p >
< p > Few implementations will do it. Clozure CL does it, SBCL does it since
its version 1.5.9 (November, 2019) or when safety is high (< code > (declaim
(optimise safety))< / code > ).< / p >
< p > To do it otherwise, see < a href = "https://stackoverflow.com/questions/51723992/how-to-force-slots-type-to-be-checked-during-make-instance" > this Stack-Overflow answer< / a > , and see also < a href = "https://github.com/sellout/quid-pro-quo" > quid-pro-quo< / a > , a contract programming library.< / p >
< h3 id = "find-class-class-name-class-of" > find-class, class-name, class-of< / h3 >
< pre > < code class = "language-lisp" > (find-class 'point)
;; #< STANDARD-CLASS POINT 275B78DC>
(class-name (find-class 'point))
;; POINT
(class-of my-point)
;; #< STANDARD-CLASS POINT 275B78DC>
(typep my-point (class-of my-point))
;; T
< / code > < / pre >
< p > CLOS classes are also instances of a CLOS class, and we can find out
what that class is, as in the example below:< / p >
< pre > < code class = "language-lisp" > (class-of (class-of my-point))
;; #< STANDARD-CLASS STANDARD-CLASS 20306534>
< / code > < / pre >
< p > < u > Note< / u > : this is your first introduction to the MOP. You don’ t need that to get started !< / p >
< p > The object < code > my-point< / code > is an instance of the class named < code > point< / code > , and the
class named < code > point< / code > is itself an instance of the class named
< code > standard-class< / code > . We say that the class named < code > standard-class< / code > is
the < em > metaclass< / em > (i.e. the class of the class) of
< code > my-point< / code > . We can make good uses of metaclasses, as we’ ll see later.< / p >
< h3 id = "subclasses-and-inheritance" > Subclasses and inheritance< / h3 >
< p > As illustrated above, < code > child< / code > is a subclass of < code > person< / code > .< / p >
< p > All objects inherit from the class < code > standard-object< / code > and < code > t< / code > .< / p >
< p > Every child instance is also an instance of < code > person< / code > .< / p >
< pre > < code class = "language-lisp" > (type-of c1)
;; CHILD
(subtypep (type-of c1) 'person)
;; T
(ql:quickload "closer-mop")
;; ...
(closer-mop:subclassp (class-of c1) 'person)
;; T
< / code > < / pre >
< p > The < a href = "https://github.com/pcostanza/closer-mop" > closer-mop< / a > library is < em > the< / em >
portable way to do CLOS/MOP operations.< / p >
< p > 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).< / p >
< p > The < strong > class precedence list< / strong > of a < code > child< / code > is thus:< / p >
< pre > < code > child < - person < -- standard-object < - t
< / code > < / pre >
< p > Which we can get with:< / p >
< pre > < code class = "language-lisp" > (closer-mop:class-precedence-list (class-of c1))
;; (#< standard-class child>
;; #< standard-class person>
;; #< standard-class standard-object>
;; #< sb-pcl::slot-class sb-pcl::slot-object>
;; #< sb-pcl:system-class t> )
< / code > < / pre >
< p > However, the < strong > direct superclass< / strong > of a < code > child< / code > is only:< / p >
< pre > < code class = "language-lisp" > (closer-mop:class-direct-superclasses (class-of c1))
;; (#< standard-class person> )
< / code > < / pre >
< p > We can further inspect our classes with
< code > class-direct-[subclasses, slots, default-initargs]< / code > and many more functions.< / p >
< p > How slots are combined follows some rules:< / p >
< ul >
< li >
< p > < code > :accessor< / code > and < code > :reader< / code > are combined by the < strong > union< / strong > of accessors
and readers from all the inherited slots.< / p >
< / li >
< li >
< p > < code > :initarg< / code > : the < strong > union< / strong > of initialization arguments from all the
inherited slots.< / p >
< / li >
< li >
< p > < code > :initform< / code > : we get < strong > the most specific< / strong > default initial value
form, i.e. the first < code > :initform< / code > for that slot in the precedence
list.< / p >
< / li >
< li >
< p > < code > :allocation< / code > is not inherited. It is controlled solely by the class
being defined and defaults to < code > :instance< / code > .< / p >
< / li >
< / ul >
< p > 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 < code > foo< / code > really wants to inherit from
< code > bar< / code > , or whether instances of < code > foo< / code > want a slot containing a < code > bar< / code > . A
good general guide is that if < code > foo< / code > and < code > bar< / code > 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.< / p >
< h3 id = "multiple-inheritance" > Multiple inheritance< / h3 >
< p > CLOS supports multiple inheritance.< / p >
< pre > < code class = "language-lisp" > (defclass baby (child person)
())
< / code > < / pre >
< p > The first class on the list of parent classes is the most specific
one, < code > child< / code > ’ s slots will take precedence over the < code > person< / code > ’ s. Note
that both < code > child< / code > and < code > person< / code > have to be defined prior to defining
< code > baby< / code > in this example.< / p >
< h3 id = "redefining-and-changing-a-class" > Redefining and changing a class< / h3 >
< p > This section briefly covers two topics:< / p >
< ul >
< li > redefinition of an existing class, which you might already have done
by following our code snippets, and what we do naturally during
development, and< / li >
< li > 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.< / li >
< / ul >
< p > We’ ll gloss over the details. Suffice it to say that everything’ s
configurable by implementing methods exposed by the MOP.< / p >
< p > To redefine a class, simply evaluate a new < code > defclass< / code > form. This then
takes the place of the old definition, the existing class object is
updated, and < strong > all instances of the class< / strong > (and, recursively, its
subclasses) < strong > are lazily updated to reflect the new definition< / strong > . You don’ t
have to recompile anything other than the new < code > defclass< / code > , nor to
invalidate any of your objects. Think about it for a second: this is awesome !< / p >
< p > For example, with our < code > person< / code > class:< / p >
< pre > < code class = "language-lisp" > (defclass person ()
((name
:initarg :name
:accessor name)
(lisper
:initform nil
:accessor lisper)))
(setf p1 (make-instance 'person :name "me" ))
< / code > < / pre >
< p > Changing, adding, removing slots,…< / p >
< pre > < code class = "language-lisp" > (lisper p1)
;; NIL
(defclass person ()
((name
:initarg :name
:accessor name)
(lisper
:initform t ;; < -- from nil to t
:accessor lisper)))
(lisper p1)
;; NIL (of course!)
(lisper (make-instance 'person :name "You"))
;; T
(defclass person ()
((name
:initarg :name
:accessor name)
(lisper
:initform nil
:accessor lisper)
(age ;; < -- new slot
:initarg :arg
:initform 18 ;; < -- default value
:accessor age)))
(age p1)
;; => 18. Correct. This is the default initform for this new slot.
(slot-value p1 'bwarf)
;; => "the slot bwarf is missing from the object #< person…> "
(setf (age p1) 30)
(age p1) ;; => 30
(defclass person ()
((name
:initarg :name
:accessor name)))
(slot-value p1 'lisper) ;; => slot lisper is missing.
(lisper p1) ;; => there is no applicable method for the generic function lisper when called with arguments #(lisper).
< / code > < / pre >
< p > To change the class of an instance, use < code > change-class< / code > :< / p >
< pre > < code class = "language-lisp" > (change-class p1 'child)
;; we can also set slots of the new class:
(change-class p1 'child :can-walk-p nil)
(class-of p1)
;; #< STANDARD-CLASS CHILD>
(can-walk-p p1)
;; T
< / code > < / pre >
< p > In the above example, I became a < code > child< / code > , and I inherited the < code > can-walk-p< / code > slot, which is true by default.< / p >
< h3 id = "pretty-printing" > Pretty printing< / h3 >
< p > Every time we printed an object so far we got an output like< / p >
< pre > < code > #< PERSON {1006234593}>
< / code > < / pre >
< p > which doesn’ t say much.< / p >
< p > What if we want to show more information ? Something like< / p >
< pre > < code > #< PERSON me lisper: t>
< / code > < / pre >
< p > Pretty printing is done by specializing the generic < code > print-object< / code > method for this class:< / p >
< pre > < code class = "language-lisp" > (defmethod print-object ((obj person) stream)
(print-unreadable-object (obj stream :type t)
(with-accessors ((name name)
(lisper lisper))
obj
(format stream "~a, lisper: ~a" name lisper))))
< / code > < / pre >
< p > It gives:< / p >
< pre > < code class = "language-lisp" > p1
;; #< PERSON me, lisper: T>
< / code > < / pre >
< p > < code > print-unreadable-object< / code > prints the < code > #< ...> < / code > , that says to the reader
that this object can not be read back in. Its < code > :type t< / code > argument asks
to print the object-type prefix, that is, < code > PERSON< / code > . Without it, we get
< code > #< me, lisper: T> < / code > .< / p >
< p > We used the < code > with-accessors< / code > macro, but of course for simple cases this is enough:< / p >
< pre > < code class = "language-lisp" > (defmethod print-object ((obj person) stream)
(print-unreadable-object (obj stream :type t)
(format stream "~a, lisper: ~a" (name obj) (lisper obj))))
< / code > < / pre >
< p > Caution: trying to access a slot that is not bound by default will
lead to an error. Use < code > slot-boundp< / code > .< / p >
< p > For reference, the following reproduces the default behaviour:< / p >
< pre > < code class = "language-lisp" > (defmethod print-object ((obj person) stream)
(print-unreadable-object (obj stream :type t :identity t)))
< / code > < / pre >
< p > Here, < code > :identity< / code > to < code > t< / code > prints the < code > {1006234593}< / code > address.< / p >
< h3 id = "classes-of-traditional-lisp-types" > Classes of traditional lisp types< / h3 >
< p > Where we approach that we don’ t need CLOS objects to use CLOS.< / p >
< p > Generously, the functions introduced in the last section also work on
lisp objects which are < u > not< / u > CLOS instances:< / p >
< pre > < code class = "language-lisp" > (find-class 'symbol)
;; #< BUILT-IN-CLASS SYMBOL>
(class-name *)
;; SYMBOL
(eq ** (class-of 'symbol))
;; T
(class-of ***)
;; #< STANDARD-CLASS BUILT-IN-CLASS>
< / code > < / pre >
< p > We see here that symbols are instances of the system class
< code > symbol< / code > . 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 < code > standard-class< / code > 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:< / p >
< style >
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
th, td {
padding: 5px;
}
th {
text-align: left;
}
< / style >
< table >
< tbody >
< tr >
< td > array< / td >
< td > hash-table< / td >
< td > readtable< / td >
< / tr >
< tr >
< td > bit-vector< / td >
< td > integer< / td >
< td > real< / td >
< / tr >
< tr >
< td > broadcast-stream< / td >
< td > list< / td >
< td > sequence< / td >
< / tr >
< tr >
< td > character< / td >
< td > logical-pathname< / td >
< td > stream< / td >
< / tr >
< tr >
< td > complex< / td >
< td > null< / td >
< td > string< / td >
< / tr >
< tr >
< td > concatenated-stream< / td >
< td > number< / td >
< td > string-stream< / td >
< / tr >
< tr >
< td > cons< / td >
< td > package< / td >
< td > symbol< / td >
< / tr >
< tr >
< td > echo-stream< / td >
< td > pathname< / td >
< td > synonym-stream< / td >
< / tr >
< tr >
< td > file-stream< / td >
< td > random-state< / td >
< td > t< / td >
< / tr >
< tr >
< td > float< / td >
< td > ratio< / td >
< td > two-way-stream< / td >
< / tr >
< tr >
< td > function< / td >
< td > rational< / td >
< td > vector< / td >
< / tr >
< / tbody >
< / table >
<!-- epub - exclude - start -->
< p > < br / >
<!-- epub - exclude - end --> < / p >
< p > Note that not all “traditional” lisp types are included in this
list. (Consider: < code > atom< / code > , < code > fixnum< / code > , < code > short-float< / code > , and any type not
denoted by a symbol.)< / p >
< p > The presence of < code > t< / code > is interesting. Just as every lisp
object is of type < code > t< / code > , every lisp object is also a member
of the class named < code > t< / code > . This is a simple example of
membership of more then one class at a time, and it brings into
question the issue of < em > inheritance< / em > , which we will consider
in some detail later.< / p >
< pre > < code class = "language-lisp" > (find-class t)
;; #< BUILT-IN-CLASS T 20305AEC>
< / code > < / pre >
< p > In addition to classes corresponding to lisp types, there is also a
CLOS class for every structure type you define:< / p >
< pre > < code class = "language-lisp" > (defstruct foo)
FOO
(class-of (make-foo))
;; #< STRUCTURE-CLASS FOO 21DE8714>
< / code > < / pre >
< p > The metaclass of a < code > structure-object< / code > is the class < code > structure-class< / code > . It is implementation-dependent whether
the metaclass of a “traditional” lisp object is < code > standard-class< / code > , < code > structure-class< / code > , or < code > built-in-class< / code > .
Restrictions:< / p >
< p > < code > built-in-class< / code > : May not use < code > make-instance< / code > , may not use < code > slot-value< / code > , may not use < code > defclass< / code > to modify, may not create subclasses.< / p >
< p > < code > structure-class< / code > : May not use < code > make-instance< / code > , might work with < code > slot-value< / code > (implementation-dependent). Use < code > defstruct< / code > to subclass application structure types. Consequences of modifying an existing < code > structure-class< / code > are undefined: full recompilation may be necessary.< / p >
< p > < code > standard-class< / code > : None of these restrictions.< / p >
< h3 id = "introspection" > Introspection< / h3 >
< p > We already saw some introspection functions.< / p >
< p > Your best option is to discover the
< a href = "https://github.com/pcostanza/closer-mop" > closer-mop< / a > library and to
keep the < a href = "https://clos-mop.hexstreamsoft.com/" > CLOS & MOP specifications< / a > at
hand.< / p >
< p > More functions:< / p >
< pre > < code > closer-mop:class-default-initargs
closer-mop:class-direct-default-initargs
closer-mop:class-direct-slots
closer-mop:class-direct-subclasses
closer-mop:class-direct-superclasses
closer-mop:class-precedence-list
closer-mop:class-slots
closer-mop:classp
closer-mop:extract-lambda-list
closer-mop:extract-specializer-names
closer-mop:generic-function-argument-precedence-order
closer-mop:generic-function-declarations
closer-mop:generic-function-lambda-list
closer-mop:generic-function-method-class
closer-mop:generic-function-method-combination
closer-mop:generic-function-methods
closer-mop:generic-function-name
closer-mop:method-combination
closer-mop:method-function
closer-mop:method-generic-function
closer-mop:method-lambda-list
closer-mop:method-specializers
closer-mop:slot-definition
closer-mop:slot-definition-allocation
closer-mop:slot-definition-initargs
closer-mop:slot-definition-initform
closer-mop:slot-definition-initfunction
closer-mop:slot-definition-location
closer-mop:slot-definition-name
closer-mop:slot-definition-readers
closer-mop:slot-definition-type
closer-mop:slot-definition-writers
closer-mop:specializer-direct-generic-functions
closer-mop:specializer-direct-methods
closer-mop:standard-accessor-method
< / code > < / pre >
< h3 id = "see-also" > See also< / h3 >
< h4 id = "defclassstd-write-shorter-classes" > defclass/std: write shorter classes< / h4 >
< p > The library < a href = "https://github.com/EuAndreh/defclass-std" > defclass/std< / a >
provides a macro to write shorter < code > defclass< / code > forms.< / p >
< p > By default, it adds an accessor, an initarg and an initform to < code > nil< / code > to your slots definition:< / p >
< p > This:< / p >
< pre > < code class = "language-lisp" > (defclass/std example ()
((slot1 slot2 slot3)))
< / code > < / pre >
< p > expands to:< / p >
< pre > < code class = "language-lisp" > (defclass example ()
((slot1
:accessor slot1
:initarg :slot1
:initform nil)
(slot2
:accessor slot2
:initarg :slot2
:initform nil)
(slot3
:accessor slot3
:initarg :slot3
:initform nil)))
< / code > < / pre >
< p > It does much more and it is very flexible, however it is seldom used
by the Common Lisp community: use at your own risk©.< / p >
< h2 id = "methods" > Methods< / h2 >
< h3 id = "diving-in-1" > Diving in< / h3 >
< p > Recalling our < code > person< / code > and < code > child< / code > classes from the beginning:< / p >
< pre > < code class = "language-lisp" > (defclass person ()
((name
:initarg :name
:accessor name)))
;; => #< STANDARD-CLASS PERSON>
(defclass child (person)
())
;; #< STANDARD-CLASS CHILD>
(setf p1 (make-instance 'person :name "me"))
(setf c1 (make-instance 'child :name "Alice"))
< / code > < / pre >
< p > Below we create methods, we specialize them, we use method combination
(before, after, around), and qualifiers.< / p >
< pre > < code class = "language-lisp" > (defmethod greet (obj)
(format t "Are you a person ? You are a ~a.~& " (type-of obj)))
;; style-warning: Implicitly creating new generic function common-lisp-user::greet.
;; #< STANDARD-METHOD GREET (t) {1008EE4603}>
(greet :anything)
;; Are you a person ? You are a KEYWORD.
;; NIL
(greet p1)
;; Are you a person ? You are a PERSON.
(defgeneric greet (obj)
(:documentation "say hello"))
;; STYLE-WARNING: redefining COMMON-LISP-USER::GREET in DEFGENERIC
;; #< STANDARD-GENERIC-FUNCTION GREET (2)>
(defmethod greet ((obj person))
(format t "Hello ~a !~& " (name obj)))
;; #< STANDARD-METHOD GREET (PERSON) {1007C26743}>
(greet p1) ;; => "Hello me !"
(greet c1) ;; => "Hello Alice !"
(defmethod greet ((obj child))
(format t "ur so cute~& "))
;; #< STANDARD-METHOD GREET (CHILD) {1008F3C1C3}>
(greet p1) ;; => "Hello me !"
(greet c1) ;; => "ur so cute"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Method combination: before, after, around.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod greet :before ((obj person))
(format t "-- before person~& "))
#< STANDARD-METHOD GREET :BEFORE (PERSON) {100C94A013}>
(greet p1)
;; -- before person
;; Hello me
(defmethod greet :before ((obj child))
(format t "-- before child~& "))
;; #< STANDARD-METHOD GREET :BEFORE (CHILD) {100AD32A43}>
(greet c1)
;; -- before child
;; -- before person
;; ur so cute
(defmethod greet :after ((obj person))
(format t "-- after person~& "))
;; #< STANDARD-METHOD GREET :AFTER (PERSON) {100CA2E1A3}>
(greet p1)
;; -- before person
;; Hello me
;; -- after person
(defmethod greet :after ((obj child))
(format t "-- after child~& "))
;; #< STANDARD-METHOD GREET :AFTER (CHILD) {10075B71F3}>
(greet c1)
;; -- before child
;; -- before person
;; ur so cute
;; -- after person
;; -- after child
(defmethod greet :around ((obj child))
(format t "Hello my dear~& "))
;; #< STANDARD-METHOD GREET :AROUND (CHILD) {10076658E3}>
(greet c1) ;; Hello my dear
;; call-next-method
(defmethod greet :around ((obj child))
(format t "Hello my dear~& ")
(when (next-method-p)
(call-next-method)))
;; #< standard-method greet :around (child) {100AF76863}>
(greet c1)
;; Hello my dear
;; -- before child
;; -- before person
;; ur so cute
;; -- after person
;; -- after child
;;;;;;;;;;;;;;;;;
;; Adding in & key
;;;;;;;;;;;;;;;;;
;; In order to add "& key" to our generic method, we need to remove its definition first.
(fmakunbound 'greet) ;; with Slime: C-c C-u (slime-undefine-function)
(defmethod greet ((obj person) & key talkative)
(format t "Hello ~a~& " (name obj))
(when talkative
(format t "blah")))
(defgeneric greet (obj & key & allow-other-keys)
(:documentation "say hi"))
(defmethod greet (obj & key & allow-other-keys)
(format t "Are you a person ? You are a ~a.~& " (type-of obj)))
(defmethod greet ((obj person) & key talkative & allow-other-keys)
(format t "Hello ~a !~& " (name obj))
(when talkative
(format t "blah")))
(greet p1 :talkative t) ;; ok
(greet p1 :foo t) ;; still ok
;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric greet (obj)
(:documentation "say hello")
(:method (obj)
(format t "Are you a person ? You are a ~a~& ." (type-of obj)))
(:method ((obj person))
(format t "Hello ~a !~& " (name obj)))
(:method ((obj child))
(format t "ur so cute~& ")))
;;;;;;;;;;;;;;;;
;;; Specializers
;;;;;;;;;;;;;;;;
(defgeneric feed (obj meal-type)
(:method (obj meal-type)
(declare (ignorable meal-type))
(format t "eating~& ")))
(defmethod feed (obj (meal-type (eql :dessert)))
(declare (ignorable meal-type))
(format t "mmh, dessert !~& "))
(feed c1 :dessert)
;; mmh, dessert !
(defmethod feed ((obj child) (meal-type (eql :soup)))
(declare (ignorable meal-type))
(format t "bwark~& "))
(feed p1 :soup)
;; eating
(feed c1 :soup)
;; bwark
< / code > < / pre >
< h3 id = "generic-functions-defgeneric-defmethod" > Generic functions (defgeneric, defmethod)< / h3 >
< p > A < code > generic function< / code > 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.< / p >
< p > The < code > defmethod< / code > form is similar to a < code > defun< / code > . 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.< / p >
< p > They can have optional, keyword and < code > & rest< / code > arguments.< / p >
< p > The < code > defgeneric< / code > form defines the generic function. If we write a
< code > defmethod< / code > without a corresponding < code > defgeneric< / code > , a generic function
is automatically created (see examples).< / p >
< p > It is generally a good idea to write the < code > defgeneric< / code > s. We can add a
default implementation and even some documentation.< / p >
< pre > < code class = "language-lisp" > (defgeneric greet (obj)
(:documentation "says hi")
(:method (obj)
(format t "Hi")))
< / code > < / pre >
< p > The required parameters in the method’ s lambda list may take one of
the following three forms:< / p >
< p > 1- a simple variable:< / p >
< pre > < code class = "language-lisp" > (defmethod greet (foo)
...)
< / code > < / pre >
< p > This method can take any argument, it is always applicable.< / p >
< p > The variable < code > foo< / code > is bound to the corresponding argument value, as
usual.< / p >
< p > 2- a variable and a < strong > specializer< / strong > , as in:< / p >
< pre > < code class = "language-lisp" > (defmethod greet ((foo person))
...)
< / code > < / pre >
< p > In this case, the variable < code > foo< / code > is bound to the corresponding
argument only if that argument is of specializer class < code > person< / code > < em > or a subclass< / em > ,
like < code > child< / code > (indeed, a “child” is also a “person”).< / p >
< p > If any argument fails to match its
specializer then the method is not < em > applicable< / em > 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”.< / p >
< p > < strong > Only required parameters can be specialized< / strong > . We can’ t specialize on optional < code > & key< / code > arguments.< / p >
< p > 3- a variable and an < strong > eql specializer< / strong > < / p >
< pre > < code class = "language-lisp" > (defmethod feed ((obj child) (meal-type (eql :soup)))
(declare (ignorable meal-type))
(format t "bwark~& "))
(feed c1 :soup)
;; "bwark"
< / code > < / pre >
< p > In place of a simple symbol (< code > :soup< / code > ), the eql specializer can be any
lisp form. It is evaluated at the same time of the defmethod.< / p >
< p > 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
< em > congruent< / em > with the shape of the generic function. The system chooses
the most < em > specific< / em > applicable method and executes its body. The most
specific method is the one whose specializers are nearest to the head
of the < code > class-precedence-list< / code > 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.< / p >
< p > < strong > Notes:< / strong > < / p >
< ul >
< li >
< p > 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.< / p >
< / li >
< li >
< p > To add or remove < code > keys< / code > or < code > rest< / code > arguments to an existing generic
method’ s lambda list, you will need to delete its declaration with
< code > fmakunbound< / code > (or < code > C-c C-u< / code > (slime-undefine-function) with the
cursor on the function in Slime) and start again. Otherwise,
you’ ll see:< / p >
< / li >
< / ul >
< pre > < code > 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.
< / code > < / pre >
< ul >
< li >
< p > Methods can be redefined (exactly as for ordinary functions).< / p >
< / li >
< li >
< p > The order in which methods are defined is irrelevant, although
any classes on which they specialize must already exist.< / p >
< / li >
< li >
< p > An unspecialized argument is more or less equivalent to being
specialized on the class < code > t< / code > . The only difference is that
all specialized arguments are implicitly taken to be “referred to” (in
the sense of < code > declare ignore< / code > .)< / p >
< / li >
< li >
< p > Each < code > defmethod< / code > form generates (and returns) a CLOS
instance, of class < code > standard-method< / code > .< / p >
< / li >
< li >
< p > An < code > eql< / code > specializer won’ t work as is with strings. Indeed, strings
need < code > equal< / code > or < code > equalp< / code > to be compared. But, we can assign our string
to a variable and use the variable both in the < code > eql< / code > specializer and
for the function call.< / p >
< / li >
< li >
< p > All the methods with the same function name belong to the same generic function.< / p >
< / li >
< li >
< p > All slot accessors and readers defined by < code > defclass< / code > are methods. They can override or be overridden by other methods on the same generic function.< / p >
< / li >
< / ul >
< p > See more about < a href = "http://www.lispworks.com/documentation/lw70/CLHS/Body/m_defmet.htm" > defmethod on the CLHS< / a > .< / p >
< h3 id = "multimethods" > Multimethods< / h3 >
< p > Multimethods explicitly specialize more than one of the generic
function’ s required parameters.< / p >
< p > They don’ t belong to a particular class. Meaning, we don’ t have to
decide on the class that would be best to host this method, as we might
have to in other languages.< / p >
< pre > < code class = "language-lisp" > (defgeneric hug (a b)
(:documentation "Hug between two persons."))
;; #< STANDARD-GENERIC-FUNCTION HUG (0)>
(defmethod hug ((a person) (b person))
:person-person-hug)
(defmethod hug ((a person) (b child))
:person-child-hug)
< / code > < / pre >
< p > Read more on < a href = "http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html#multimethods" > Practical Common Lisp< / a > .< / p >
< h3 id = "controlling-setters-setf-ing-methods" > Controlling setters (setf-ing methods)< / h3 >
< p > In Lisp, we can define < code > setf< / code > counterparts of functions or methods. We
might want this to have more control on how to update an object.< / p >
< pre > < code class = "language-lisp" > (defmethod (setf name) (new-val (obj person))
(if (equalp new-val "james bond")
(format t "Dude that's not possible.~& ")
(setf (slot-value obj 'name) new-val)))
(setf (name p1) "james bond") ;; -> no rename
< / code > < / pre >
< p > If you know Python, this behaviour is provided by the < code > @property< / code > decorator.< / p >
< h3 id = "dispatch-mechanism-and-next-methods" > Dispatch mechanism and next methods< / h3 >
< p > When a generic function is invoked, the application cannot directly invoke a method. The dispatch mechanism proceeds as follows:< / p >
< ol >
< li > compute the list of applicable methods< / li >
< li > if no method is applicable then signal an error< / li >
< li > sort the applicable methods in order of specificity< / li >
< li > invoke the most specific method.< / li >
< / ol >
< p > Our < code > greet< / code > generic function has three applicable methods:< / p >
< pre > < code class = "language-lisp" > (closer-mop:generic-function-methods #'greet)
(#< STANDARD-METHOD GREET (CHILD) {10098406A3}>
#< STANDARD-METHOD GREET (PERSON) {1009008EC3}>
#< STANDARD-METHOD GREET (T) {1008E6EBB3}> )
< / code > < / pre >
< p > During the execution of a method, the remaining applicable methods
are still accessible, via the < em > local function< / em >
< code > call-next-method< / code > . 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:< / p >
< ul >
< li >
< p > no arguments, in which case the < em > next method< / em > will
receive exactly the same arguments as this method did, or< / p >
< / li >
< li >
< p > 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.< / p >
< / li >
< / ul >
< p > For example:< / p >
< pre > < code class = "language-lisp" > (defmethod greet ((obj child))
(format t "ur so cute~& ")
(when (next-method-p)
(call-next-method)))
;; STYLE-WARNING: REDEFINING GREET (#< STANDARD-CLASS CHILD> ) in DEFMETHOD
;; #< STANDARD-METHOD GREET (child) {1003D3DB43}>
(greet c1)
;; ur so cute
;; Hello Alice !
< / code > < / pre >
< p > Calling < code > call-next-method< / code > when there is no next method
signals an error. You can find out whether a next method exists by
calling the local function < code > next-method-p< / code > (which also has
has lexical scope and indefinite extent).< / p >
< p > Note finally that the body of every method establishes a block with the same name as the method’ s generic function. If you < code > return-from< / code > that name you are exiting the current method, not the call to the enclosing generic function.< / p >
< h3 id = "method-qualifiers-before-after-around" > Method qualifiers (before, after, around)< / h3 >
< p > In our “Diving in” examples, we saw some use of the < code > :before< / code > , < code > :after< / code > and < code > :around< / code > < em > qualifiers< / em > :< / p >
< ul >
< li > < code > (defmethod foo :before (obj) (...))< / code > < / li >
< li > < code > (defmethod foo :after (obj) (...))< / code > < / li >
< li > < code > (defmethod foo :around (obj) (...))< / code > < / li >
< / ul >
< p > By default, in the < em > standard method combination< / em > framework provided by
CLOS, we can only use one of those three qualifiers, and the flow of control is as follows:< / p >
< ul >
< li > a < strong > before-method< / strong > is called, well, before the applicable primary
method. If they are many before-methods, < strong > all< / strong > are called. The
most specific before-method is called first (child before person).< / li >
< li > the most specific applicable < strong > primary method< / strong > (a method without
qualifiers) is called (only one).< / li >
< li > all applicable < strong > after-methods< / strong > are called. The most specific one is
called < em > last< / em > (after-method of person, then after-method of child).< / li >
< / ul >
< p > < strong > The generic function returns the value of the primary method< / strong > . Any
values of the before or after methods are ignored. They are used for
their side effects.< / p >
< p > And then we have < strong > around-methods< / strong > . 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,…).< / p >
< p > If the dispatch mechanism finds an around-method, it calls it and
returns its result. If the around-method has a < code > call-next-method< / code > , 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.< / p >
< p > Thus, the full dispatch mechanism for generic functions is as follows:< / p >
< ol >
< li > compute the applicable methods, and partition them into
separate lists according to their qualifier;< / li >
< li > if there is no applicable primary method then signal an
error;< / li >
< li > sort each of the lists into order of specificity;< / li >
< li > execute the most specific < code > :around< / code > method and
return whatever that returns;< / li >
< li > if an < code > :around< / code > method invokes
< code > call-next-method< / code > , execute the next most specific
< code > :around< / code > method;< / li >
< li >
< p > if there were no < code > :around< / code > methods in the first
place, or if an < code > :around< / code > method invokes
< code > call-next-method< / code > but there are no further
< code > :around< / code > methods to call, then proceed as follows:< / p >
< p > a. run all the < code > :before< / code > methods, in order,
ignoring any return values and not permitting calls to
< code > call-next-method< / code > or
< code > next-method-p< / code > ;< / p >
< p > b. execute the most specific primary method and return
whatever that returns;< / p >
< p > c. if a primary method invokes < code > call-next-method< / code > ,
execute the next most specific primary method;< / p >
< p > d. if a primary method invokes < code > call-next-method< / code >
but there are no further primary methods to call then signal an
error;< / p >
< p > e. after the primary method(s) have completed, run all the
< code > :after< / code > methods, in < strong > < u > reverse< / u > < / strong >
order, ignoring any return values and not permitting calls to
< code > call-next-method< / code > or
< code > next-method-p< / code > .< / p >
< / li >
< / ol >
< p > Think of it as an onion, with all the < code > :around< / code >
methods in the outermost layer, < code > :before< / code > and
< code > :after< / code > methods in the middle layer, and primary methods
on the inside.< / p >
< h3 id = "other-method-combinations" > Other method combinations< / h3 >
< p > The default method combination type we just saw is named < code > standard< / code > ,
but other method combination types are available, and no need to say
that you can define your own.< / p >
< p > The built-in types are:< / p >
< pre > < code > progn + list nconc and max or append min
< / code > < / pre >
< p > 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 < code > progn< / code > combination type is equivalent to calling < strong > all< / strong >
the primary methods one after the other:< / p >
< pre > < code class = "language-lisp" > (progn
(method-1 args)
(method-2 args)
(method-3 args))
< / code > < / pre >
< p > Here, unlike the standard mechanism, all the primary methods
applicable for a given object are called, the most specific
first.< / p >
< p > To change the combination type, we set the < code > :method-combination< / code >
option of < code > defgeneric< / code > and we use it as the methods’ qualifier:< / p >
< pre > < code class = "language-lisp" > (defgeneric foo (obj)
(:method-combination progn))
(defmethod foo progn ((obj obj))
(...))
< / code > < / pre >
< p > An example with < strong > progn< / strong > :< / p >
< pre > < code class = "language-lisp" > (defgeneric dishes (obj)
(:method-combination progn)
(:method progn (obj)
(format t "- clean and dry.~& "))
(:method progn ((obj person))
(format t "- bring a person's dishes~& "))
(:method progn ((obj child))
(format t "- bring the baby dishes~& ")))
;; #< STANDARD-GENERIC-FUNCTION DISHES (3)>
(dishes c1)
;; - bring the baby dishes
;; - bring a person's dishes
;; - clean and dry.
(greet c1)
;; ur so cute --> only the most applicable method was called.
< / code > < / pre >
< p > Similarly, using the < code > list< / code > type is equivalent to returning the list
of the values of the methods.< / p >
< pre > < code class = "language-lisp" > (list
(method-1 args)
(method-2 args)
(method-3 args))
< / code > < / pre >
< pre > < code class = "language-lisp" > (defgeneric tidy (obj)
(:method-combination list)
(:method list (obj)
:foo)
(:method list ((obj person))
:books)
(:method list ((obj child))
:toys))
;; #< STANDARD-GENERIC-FUNCTION TIDY (3)>
(tidy c1)
;; (:toys :books :foo)
< / code > < / pre >
< p > < strong > Around methods< / strong > are accepted:< / p >
< pre > < code class = "language-lisp" > (defmethod tidy :around (obj)
(let ((res (call-next-method)))
(format t "I'm going to clean up ~a~& " res)
(when (> (length res)
1)
(format t "that's too much !~& "))))
(tidy c1)
;; I'm going to clean up (toys book foo)
;; that's too much !
< / code > < / pre >
< p > Note that these operators don’ t support < code > before< / code > , < code > after< / code > and < code > around< / code >
methods (indeed, there is no room for them anymore). They do support
around methods, where < code > call-next-method< / code > is allowed, but they don’ t
support calling < code > call-next-method< / code > in the primary methods (it would
indeed be redundant since all primary methods are called, or clunky to
< em > not< / em > call one).< / p >
< p > 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.< / p >
< h3 id = "debugging-tracing-method-combination" > Debugging: tracing method combination< / h3 >
< p > It is possible to < a href = "http://www.xach.com/clhs?q=trace" > trace< / a > the method
combination, but this is implementation dependent.< / p >
< p > In SBCL, we can use < code > (trace foo :methods t)< / code > . See < a href = "http://christophe.rhodes.io/notes/blog/posts/2018/sbcl_method_tracing/" > this post by an SBCL core developer< / a > .< / p >
< p > For example, given a generic:< / p >
< pre > < code class = "language-lisp" > (defgeneric foo (x)
(:method (x) 3))
(defmethod foo :around ((x fixnum))
(1+ (call-next-method)))
(defmethod foo ((x integer))
(* 2 (call-next-method)))
(defmethod foo ((x float))
(* 3 (call-next-method)))
(defmethod foo :before ((x single-float))
'single)
(defmethod foo :after ((x double-float))
'double)
< / code > < / pre >
< p > Let’ s trace it:< / p >
< pre > < code class = "language-lisp" > (trace foo :methods t)
(foo 2.0d0)
0: (FOO 2.0d0)
1: ((SB-PCL::COMBINED-METHOD FOO) 2.0d0)
2: ((METHOD FOO (FLOAT)) 2.0d0)
3: ((METHOD FOO (T)) 2.0d0)
3: (METHOD FOO (T)) returned 3
2: (METHOD FOO (FLOAT)) returned 9
2: ((METHOD FOO :AFTER (DOUBLE-FLOAT)) 2.0d0)
2: (METHOD FOO :AFTER (DOUBLE-FLOAT)) returned DOUBLE
1: (SB-PCL::COMBINED-METHOD FOO) returned 9
0: FOO returned 9
9
< / code > < / pre >
2024-01-12 09:23:31 +01:00
< h3 id = "difference-between-defgeneric-and-defmethod-redefinition" > Difference between defgeneric and defmethod: redefinition< / h3 >
< p > There is a difference between declaring methods inside a < code > defgeneric< / code >
body or by writing multiple < code > defmethod< / code > s: the two methods handle
re-definition of methods differently. < code > defgeneric< / code > will delete methods
that are not in its body anymore.< / p >
< p > Below we define a new generic function, using two < code > defmethod< / code > that
specialize on < code > person< / code > and < code > child< / code > :< / p >
< pre > < code class = "language-lisp" > (defmethod goodbye ((p person))
(format t "goodbye ~a.~& " (name p)))
(defmethod goodbye ((c child))
(format t "love you lil' one < 3~& "))
< / code > < / pre >
< p > You can try them with < code > (goodbye (make-instance 'person :name "you"))< / code > .< / p >
< p > Now, later in your work session, you decide that you don’ t need the
one specializing on < code > child< / code > any more. You delete its source code. But
< strong > the method still exists in the image< / strong > . You have to programmatically
remove the method, see below.< / p >
< p > Had you used < code > defgeneric< / code > , all the methods would have been updated,
added or deleted. We have defined the < code > tidy< / code > generic function already
with three methods:< / p >
< pre > < code class = "language-lisp" > (defgeneric tidy (obj)
(:method-combination list)
(:method list (obj)
:foo)
(:method list ((obj person))
:books)
(:method list ((obj child))
:toys))
< / code > < / pre >
< p > It works for any object type, a person or a child. Try it on a string:
< code > (tidy "tidy what?")< / code > , it works.< / p >
< p > Now remove this declaration from the < code > defgeneric< / code > :< / p >
< pre > < code class = "language-lisp" > (defgeneric tidy (obj)
(:method-combination list)
;;(:method list (obj) ;; < --- commented out
;; :foo)
(:method list ((obj person))
:books)
(:method list ((obj child))
:toys))
< / code > < / pre >
< p > Try to call it again: you get a “no applicable method” error:< / p >
< pre > < code > There is no applicable method for the generic function
#< STANDARD-GENERIC-FUNCTION TRADESIGNAL::TIDY (2)>
when called with arguments
("tidy what?").
< / code > < / pre >
< p > This might or might not be important to you during development, but
knowing this can help you keep your lisp image in sync with your
source code. Otherwise, you can remove an old method when it gets on
your way.< / p >
< h3 id = "removing-a-method" > Removing a method< / h3 >
< p > First, we need to find the method object:< / p >
< pre > < code class = "language-lisp" > (find-method #'goodbye nil (list (find-class 'child)))
;; => #< STANDARD-METHOD GOODBYE (CHILD) {10073EFD73}>
< / code > < / pre >
< p > < code > find-method< / code > takes as arguments: a function reference, a qualifier
(like before, after or around), and a list of class specializers.< / p >
< p > Once you found the method, use < code > remove-method< / code > .< / p >
< p > You could use < code > (fmakunbound 'goodbye)< / code > , but this makes < em > all< / em > methods
unbound.< / p >
2023-10-25 11:23:21 +02:00
< h2 id = "mop" > MOP< / h2 >
< p > 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.< / p >
< p > 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.< / p >
< h3 id = "metaclasses" > Metaclasses< / h3 >
< p > Metaclasses are needed to control the behaviour of other classes.< / p >
< p > < em > As announced, we won’ t talk much. See also Wikipedia for < a href = "https://en.wikipedia.org/wiki/Metaclass" > metaclasses< / a > or < a href = "https://en.wikipedia.org/wiki/Common_Lisp_Object_System" > CLOS< / a > < / em > .< / p >
< p > The standard metaclass is < code > standard-class< / code > :< / p >
< pre > < code class = "language-lisp" > (class-of p1) ;; #< STANDARD-CLASS PERSON>
< / code > < / pre >
< p > But we’ ll change it to one of our own, so that we’ ll be able to
< strong > count the creation of instances< / strong > . 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.< / p >
< p > Our metaclass inherits from < code > standard-class< / code > :< / p >
< pre > < code class = "language-lisp" > (defclass counted-class (standard-class)
((counter :initform 0)))
#< STANDARD-CLASS COUNTED-CLASS>
(unintern 'person)
;; this is necessary to change the metaclass of person.
;; or (setf (find-class 'person) nil)
;; https://stackoverflow.com/questions/38811931/how-to-change-classs-metaclass#38812140
(defclass person ()
((name
:initarg :name
:accessor name))
(:metaclass counted-class)) ;; < - metaclass
;; #< COUNTED-CLASS PERSON>
;; ^^^ not standard-class anymore.
< / code > < / pre >
< p > The < code > :metaclass< / code > class option can appear only once.< / p >
< p > Actually you should have gotten a message asking to implement
< code > validate-superclass< / code > . So, still with the < code > closer-mop< / code > library:< / p >
< pre > < code class = "language-lisp" > (defmethod closer-mop:validate-superclass ((class counted-class)
(superclass standard-class))
t)
< / code > < / pre >
< p > Now we can control the creation of new < code > person< / code > instances:< / p >
< pre > < code class = "language-lisp" > (defmethod make-instance :after ((class counted-class) & key)
(incf (slot-value class 'counter)))
;; #< STANDARD-METHOD MAKE-INSTANCE :AFTER (COUNTED-CLASS) {1007718473}>
< / code > < / pre >
< p > See that an < code > :after< / code > qualifier is the safest choice, we let the
standard method run as usual and return a new instance.< / p >
< p > The < code > & key< / code > is necessary, remember that < code > make-instance< / code > is given initargs.< / p >
< p > Now testing:< / p >
< pre > < code class = "language-lisp" > (defvar p3 (make-instance 'person :name "adam"))
#< PERSON {1007A8F5B3}>
(slot-value p3 'counter)
;; => error. No, our new slot isn't on the person class.
(slot-value (find-class 'person) 'counter)
;; 1
(make-instance 'person :name "eve")
;; #< PERSON {1007AD5773}>
(slot-value (find-class 'person) 'counter)
;; 2
< / code > < / pre >
< p > It’ s working.< / p >
< h3 id = "controlling-the-initialization-of-instances-initialize-instance" > Controlling the initialization of instances (initialize-instance)< / h3 >
< p > To further control the creation of object instances, we can specialize the method
< code > initialize-instance< / code > . It is called by < code > make-instance< / code > , just after
a new instance was created but wasn’ t initialized yet with the
default initargs and initforms.< / p >
< p > It is recommended (Keene) to create an after method, since creating a
primary method would prevent slots’ initialization.< / p >
2024-01-12 09:23:31 +01:00
< pre > < code class = "language-lisp" > (defmethod initialize-instance :after ((obj person) & key)
;; note the & key in the arglist: ^^^^
2023-10-25 11:23:21 +02:00
(do something with obj))
< / code > < / pre >
< p > A typical example would be to validate the initial values. Here we’ ll
check that the person’ s name is longer than 3 characters:< / p >
< pre > < code class = "language-lisp" > (defmethod initialize-instance :after ((obj person) & key)
(with-slots (name) obj
(assert (> = (length name) 3))))
< / code > < / pre >
< p > So this call doesn’ t work anymore:< / p >
2024-01-12 09:23:31 +01:00
< pre > < code class = "language-lisp" > (make-instance 'person :name "me")
2023-10-25 11:23:21 +02:00
;; The assertion (> = #1=(LENGTH NAME) 3) failed with #1# = 2.
;; [Condition of type SIMPLE-ERROR]
< / code > < / pre >
< p > We are prompted into the interactive debugger and we are given a
choice of restarts (continue, retry, abort).< / p >
< p > So while we’ re at it, here’ s an assertion that uses the debugger
2024-01-12 09:23:31 +01:00
features to offer to change “name”. We give < code > assert< / code > a list of places
that can be changed from the debugger:< / p >
2023-10-25 11:23:21 +02:00
< pre > < code class = "language-lisp" > (defmethod INITIALIZE-INSTANCE :after ((obj person) & key)
(with-slots (name) obj
(assert (> = (length name) 3)
2024-01-12 09:23:31 +01:00
(name) ;; < -- list of places
2023-10-25 11:23:21 +02:00
"The value of name is ~a. It should be longer than 3 characters." name)))
< / code > < / pre >
< p > We get:< / p >
< pre > < code > The value of name is me. It should be longer than 3 characters.
[Condition of type SIMPLE-ERROR]
Restarts:
2024-01-12 09:23:31 +01:00
0: [CONTINUE] Retry assertion with new value for NAME.
^^^^^^^^^^^^ our new restart
2023-10-25 11:23:21 +02:00
1: [RETRY] Retry SLIME REPL evaluation request.
2: [*ABORT] Return to SLIME's top level.
< / code > < / pre >
< p > Another rationale. The CLOS implementation of
< code > make-instance< / code > is in two stages: allocate the new object,
and then pass it along with all the < code > make-instance< / code > keyword
arguments, to the generic function
< code > initialize-instance< / code > . Implementors and application writers
define < code > :after< / code > methods on
< code > initialize-instance< / code > , to initialize the slots of the
instance. The system-supplied primary method does this with regard to
(a) < code > :initform< / code > and < code > :initarg< / code > values supplied
with the class was defined and (b) the keywords passed through from
< code > make-instance< / code > . 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 < code > initialize-instance< / code > is:< / p >
< pre > < code > initialize-instance instance & rest initargs & key & allow-other-keys
< / code > < / pre >
2024-01-12 09:23:31 +01:00
< h3 id = "controlling-the-update-of-instances-update-instance-for-redefined-class" > Controlling the update of instances (update-instance-for-redefined-class)< / h3 >
< p > Suppose you created a “circle” class, with coordinates and a
diameter. Later on, you decide to replace the diameter by a
radius. You want all the existing objects to be cleverly updated:
the radius should have the diameter value, divided by 2. Use
< code > update-instance-for-redefined-class< / code > .< / p >
< p > Its parameters are:< / p >
< ul >
< li > instance: the object instance that is being updated< / li >
< li > added-slots: a list of added slots< / li >
< li > discarded-slots: a list of discarded slots< / li >
< li > property-list: a plist that captured the slot names and values of all the discarded-slots with values in the original instance.< / li >
< li > initargs: an initialization argument list. < code > & key< / code > catches them below.< / li >
< / ul >
< p > and it returns an object.< / p >
< p > We actually don’ t call the method direcly, but we use a < code > :before< / code > method:< / p >
< pre > < code class = "language-lisp" > (defmethod update-instance-for-redefined-class
:before ((obj circle) added deleted plist-values & key)
(format t "plist values: ~a~& " plist-values)
(let ((diameter (getf plist-values 'diameter)))
(setf (radius obj) (/ diameter 2))))
< / code > < / pre >
< p > Here’ s how to try it. Start with a < code > circle< / code > class:< / p >
< pre > < code class = "language-lisp" > (defclass circle ()
((diameter :accessor diameter :initform 9)))
< / code > < / pre >
< p > and create a circle object:< / p >
< pre > < code class = "language-lisp" > (make-instance 'circle)
< / code > < / pre >
< p > inspect it or check its diameter value.< / p >
< p > Now write and compile a new class definition:< / p >
< pre > < code class = "language-lisp" > (defclass circle ()
((radius :accessor radius)))
< / code > < / pre >
< p > Nothing happens yet, you don’ t see the output of our “plist values” print.< / p >
< p > Inspect or < code > describe< / code > the object: now it will be updated, and you’ ll
find the < code > radius< / code > slot.< / p >
< p > Existing objects are updated lazily.< / p >
< p > See more on the < a href = "https://www.lispworks.com/documentation/HyperSpec/Body/f_upda_1.htm" > HyperSpec< / a >
or on the < a href = "https://cl-community-spec.github.io/pages/update_002dinstance_002dfor_002dredefined_002dclass.html" > Community Spec< / a > .< / p >
< h3 id = "controlling-the-update-of-instances-to-new-classes-update-instance-for-different-class" > Controlling the update of instances to new classes (update-instance-for-different-class)< / h3 >
< p > Now imagine you are working with the < code > circle< / code > class, but you realize
you only need a < code > surface< / code > kind of objects. You will discard the circle
class altogether, but you want your existing objects to be updated -to
this new class, and compute new slots intelligently. Use
< code > update-instance-for-different-class< / code > .< / p >
< p > See more on the < a href = "https://www.lispworks.com/documentation/HyperSpec/Body/f_update.htm" > HyperSpec< / a > or on the < a href = "https://cl-community-spec.github.io/pages/update_002dinstance_002dfor_002ddifferent_002dclass.html" > Community Spec< / a > .< / p >
< p > And see more in the books!< / p >
2023-10-25 11:23:21 +02:00
< p class = "page-source" >
Page source: < a href = "https://github.com/LispCookbook/cl-cookbook/blob/master/clos.md" > clos.md< / a >
< / p >
< / div >
< script type = "text/javascript" >
// Don't write the TOC on the index.
if (window.location.pathname != "/cl-cookbook/") {
$("#toc").toc({
content: "#content", // will ignore the first h1 with the site+page title.
headings: "h1,h2,h3,h4"});
}
$("#two-cols + ul").css({
"column-count": "2",
});
$("#contributors + ul").css({
"column-count": "4",
});
< / script >
< div >
< footer class = "footer" >
< hr / >
© 2002– 2023 the Common Lisp Cookbook Project
< div >
📹 Discover < a style = "color: darkgrey; text-decoration: underline" , href = "https://www.udemy.com/course/common-lisp-programming/?referralCode=2F3D698BBC4326F94358" > vindarel's Lisp course on Udemy< / a >
< / div >
< / footer >
< / div >
< div id = "toc-btn" > T< br > O< br > C< / div >
< / div >
< script text = "javascript" >
HighlightLisp.highlight_auto({className: null});
< / script >
< script type = "text/javascript" >
function duckSearch() {
var searchField = document.getElementById("searchField");
if (searchField & & searchField.value) {
var query = escape("site:lispcookbook.github.io/cl-cookbook/ " + searchField.value);
window.location.href = "https://duckduckgo.com/?kj=b2& kf=-1& ko=1& q=" + query;
// https://duckduckgo.com/params
// kj=b2: blue header in results page
// kf=-1: no favicons
}
}
< / script >
< script async defer data-domain = "lispcookbook.github.io/cl-cookbook" src = "https://plausible.io/js/plausible.js" > < / script >
< / body >
< / html >