1931 lines
66 KiB
HTML
1931 lines
66 KiB
HTML
<!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">
|
||
📢 🤶 ⭐
|
||
<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>
|
||
<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>
|
||
|
||
<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
|
||
(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>
|
||
|
||
<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>
|
||
|
||
<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>
|
||
|
||
<pre><code class="language-lisp">(defmethod initialize-instance :after ((obj person) &key)
|
||
;; note the &key in the arglist: ^^^^
|
||
(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>
|
||
|
||
<pre><code class="language-lisp">(make-instance 'person :name "me")
|
||
;; 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
|
||
features to offer to change “name”. We give <code>assert</code> a list of places
|
||
that can be changed from the debugger:</p>
|
||
|
||
<pre><code class="language-lisp">(defmethod INITIALIZE-INSTANCE :after ((obj person) &key)
|
||
(with-slots (name) obj
|
||
(assert (>= (length name) 3)
|
||
(name) ;; <-- list of places
|
||
"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:
|
||
0: [CONTINUE] Retry assertion with new value for NAME.
|
||
^^^^^^^^^^^^ our new restart
|
||
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>
|
||
|
||
<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>
|
||
|
||
|
||
<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>
|