emacs.d/clones/lisp/stevelosh.com/blog/2022/08/depending-in-common-lisp/index.html

939 lines
81 KiB
HTML
Raw Normal View History

2022-10-07 15:47:14 +02:00
<!DOCTYPE html>
<html lang='en'><head><meta charset='utf-8' /><meta name='pinterest' content='nopin' /><link href='../../../../static/css/style.css' rel='stylesheet' type='text/css' /><link href='../../../../static/css/print.css' rel='stylesheet' type='text/css' media='print' /><title>Depending in Common Lisp / Steve Losh</title></head><body><header><a id='logo' href='https://stevelosh.com/'>Steve Losh</a><nav><a href='../../../index.html'>Blog</a> - <a href='https://stevelosh.com/projects/'>Projects</a> - <a href='https://stevelosh.com/photography/'>Photography</a> - <a href='https://stevelosh.com/links/'>Links</a> - <a href='https://stevelosh.com/rss.xml'>Feed</a></nav></header><hr class='main-separator' /><main id='page-blog-entry'><article><h1><a href='index.html'>Depending in Common Lisp</a></h1><p class='date'>Posted on August 26th, 2022.</p><p>A while ago I was working on a Common Lisp library that makes use of the
<a href="http://metamodular.com/CLOS-MOP/">Metaobject Protocol</a>. I ran into a few edge
cases around dependencies between classes and it took a while for me to figure
out how to solve them, so I wanted to write down what I learned in case anyone
else might find it useful. This post is an expanded version of <a href="https://www.reddit.com/r/Common_Lisp/comments/kljyg1/need_advice_on_how_to_handle_metaclass_option/">a Reddit
thread</a> I posted.</p>
<ol class="table-of-contents"><li><a href="index.html#s1-setting-the-stage">Setting the Stage</a><ol><li><a href="index.html#s2-adding-more-flexibility">Adding More Flexibility</a></li><li><a href="index.html#s3-toy-example-disclaimer">Toy Example Disclaimer</a></li></ol></li><li><a href="index.html#s4-the-problem">The Problem</a></li><li><a href="index.html#s5-the-dependent-maintenance-protocol">The Dependent Maintenance Protocol</a><ol><li><a href="index.html#s6-dependency-wrappers">Dependency Wrappers</a></li><li><a href="index.html#s7-defining-the-metaclass">Defining the Metaclass</a></li><li><a href="index.html#s8-computing-slots">Computing Slots</a></li><li><a href="index.html#s9-initialization">Initialization</a></li><li><a href="index.html#s10-reinitialization">Reinitialization</a></li><li><a href="index.html#s11-dependent-updates">Dependent Updates</a></li></ol></li><li><a href="index.html#s12-the-result">The Result</a></li><li><a href="index.html#s13-is-it-worth-it">Is It Worth It?</a></li></ol>
<h2 id="s1-setting-the-stage"><a href="index.html#s1-setting-the-stage">Setting the Stage</a></h2>
<p>Before we can see the problem, we need a simple example. We'll use the
<code>monitored-class</code> metaclass from <a href="https://en.wikipedia.org/wiki/The_Art_of_the_Metaobject_Protocol">The Art of the Metaobject Protocol</a>
(pages 96-97). Using this class as a metaclass will log all slot reads and
writes, which could be useful for auditing access to certain objects.</p>
<p>Before we get started we'll need <a href="https://github.com/pcostanza/closer-mop">Closer to MOP</a> as an
implementation compatibility layer:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">ql:quickload <span class="keyword">:closer-mop</span></span>)</span></span></code></pre>
<p>First we define the metaclass:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> monitored-class <span class="paren2">(<span class="code">standard-class</span>)</span>
<span class="paren2">(<span class="code"></span>)</span></span>)</span></span></code></pre>
<p>Next we'll explicitly say that it's okay for a monitored class to have
superclasses that are standard classes:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> c2mop:validate-superclass
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">class monitored-class</span>)</span> <span class="paren3">(<span class="code">superclass standard-class</span>)</span></span>)</span>
t</span>)</span></span></code></pre>
<p>And now we can define the actual monitoring functionality. We'll use <code>:before</code>
methods on <code>slot-value-using-class</code> and its <code>setf</code> version to log the reads and
writes of all slots:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> c2mop:slot-value-using-class <span class="keyword">:before</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">class monitored-class</span>)</span> instance slot</span>)</span>
<span class="paren2">(<span class="code">format t <span class="string">&quot;Reading slot ~A of ~A at ~A.~%&quot;</span>
<span class="paren3">(<span class="code">c2mop:slot-definition-name slot</span>)</span> instance <span class="paren3">(<span class="code">get-universal-time</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> <span class="paren2">(<span class="code">setf c2mop:slot-value-using-class</span>)</span> <span class="keyword">:before</span>
<span class="paren2">(<span class="code">new-value <span class="paren3">(<span class="code">class monitored-class</span>)</span> instance slot</span>)</span>
<span class="paren2">(<span class="code">format t <span class="string">&quot;Setting slot ~A of ~A to ~S at ~A.~%&quot;</span>
<span class="paren3">(<span class="code">c2mop:slot-definition-name slot</span>)</span> instance new-value <span class="paren3">(<span class="code">get-universal-time</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>With that complete, we can define a new monitored class:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> user <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">id <span class="keyword">:initarg</span> <span class="keyword">:id</span></span>)</span>
<span class="paren3">(<span class="code">name <span class="keyword">:initarg</span> <span class="keyword">:name</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span></span>)</span></span></code></pre>
<p>And now we can see it in action:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*u*</span> <span class="paren2">(<span class="code">make-instance 'user <span class="keyword">:id</span> 1 <span class="keyword">:name</span> <span class="string">&quot;sjl&quot;</span></span>)</span></span>)</span>
<span class="comment">; =&gt; Setting slot ID of #&lt;USER {10074DFD33}&gt; to 1 at 3828527923.
</span><span class="comment">; =&gt; Setting slot NAME of #&lt;USER {10074DFD33}&gt; to &quot;sjl&quot; at 3828527923.
</span><span class="comment">; =&gt; *U*
</span>
<span class="paren1">(<span class="code">slot-value <span class="special">*u*</span> 'id</span>)</span>
<span class="comment">; =&gt; Reading slot ID of #&lt;USER {10074DFD33}&gt; at 3828527937.
</span><span class="comment">; =&gt; 1
</span>
<span class="paren1">(<span class="code">setf <span class="paren2">(<span class="code">slot-value <span class="special">*u*</span> 'name</span>)</span> <span class="string">&quot;Steve&quot;</span></span>)</span>
<span class="comment">; =&gt; Setting slot NAME of #&lt;USER {10074DFD33}&gt; to &quot;Steve&quot; at 3828527946.
</span><span class="comment">; =&gt; &quot;Steve&quot;</span></span></code></pre>
<h3 id="s2-adding-more-flexibility"><a href="index.html#s2-adding-more-flexibility">Adding More Flexibility</a></h3>
<p>Now that we have a toy example working, let's make it a little more flexible.
Instead of always generating a string and writing it to standard out, we'll
allow users to provide a <code>:monitoring-function</code> as a class option that will
receive the data and can do whatever it wants. For example:</p>
<ul>
<li>Logging to syslog instead of standard out.</li>
<li>Inserting a row into a Postgres database as an audit log.</li>
<li>Tracking read/write counts in a hash table to find slots that are written more
often than they're read and vice versa.</li>
</ul>
<p>A monitoring function will receive 2 arguments (the instance and slot name),
plus an optional third argument when a slot is written (the new value). We can
make a default monitoring function that works the same way as before:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> log-slot-access <span class="paren2">(<span class="code">instance slot-name &amp;optional <span class="paren3">(<span class="code">new-value nil new-value?</span>)</span></span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">if</span></i> new-value?
<span class="paren3">(<span class="code">format t <span class="string">&quot;Setting slot ~A of ~A to ~S at ~A.~%&quot;</span>
slot-name instance new-value <span class="paren4">(<span class="code">get-universal-time</span>)</span></span>)</span>
<span class="paren3">(<span class="code">format t <span class="string">&quot;Reading slot ~A of ~A at ~A.~%&quot;</span>
slot-name instance <span class="paren4">(<span class="code">get-universal-time</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Note the use of the extended <code>&amp;optional</code> form with the
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/03_dab.htm"><code>supplied-p-parameter</code></a> used to check whether a value was given,
which ensures this works correctly even when setting a slot to <code>nil</code>.</p>
<p>Also note how we called it <code>new-value?</code> and not <code>new-value-p</code> as you'll
sometimes see people do. The <code>-p</code> in <code>new-value-p</code> stands for &quot;predicate&quot;, and
a <a href="http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_p.htm#predicate">predicate</a> is a <em>function</em> that returns a (generalized) boolean,
<em>not</em> a boolean itself. Using a name that ends in <code>-p</code> for a boolean value
(rather than for a predicate) is a <code>-p</code>et <code>-p</code>eeve of mine. Unfortunately it
happens in a couple of places (even in Common Lisp itself), so it's something to
watch out for.</p>
<p>Now we can update our <code>monitored-class</code> to add a slot to store the monitoring
function for each class, and update the <code>slot-value-using-class</code> methods to use
that instead of writing the string themselves:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> monitored-class <span class="paren2">(<span class="code">standard-class</span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">monitoring-function <span class="keyword">:initarg</span> <span class="keyword">:monitoring-function</span>
<span class="keyword">:accessor</span> monitoring-function</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> c2mop:slot-value-using-class <span class="keyword">:before</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">class monitored-class</span>)</span> instance slot</span>)</span>
<span class="paren2">(<span class="code">funcall <span class="paren3">(<span class="code">monitoring-function class</span>)</span>
instance
<span class="paren3">(<span class="code">c2mop:slot-definition-name slot</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> <span class="paren2">(<span class="code">setf c2mop:slot-value-using-class</span>)</span> <span class="keyword">:before</span>
<span class="paren2">(<span class="code">new-value <span class="paren3">(<span class="code">class monitored-class</span>)</span> instance slot</span>)</span>
<span class="paren2">(<span class="code">funcall <span class="paren3">(<span class="code">monitoring-function class</span>)</span>
instance
<span class="paren3">(<span class="code">c2mop:slot-definition-name slot</span>)</span>
new-value</span>)</span></span>)</span></span></code></pre>
<p>When a user creates a new <code>monitored-class</code>, we need to set the
<code>monitoring-function</code> slot appropriately. We might initially consider doing
this by having an <code>initform</code> for the <code>monitoring-function</code> slot in the
metaclass, like this:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> monitored-class <span class="paren2">(<span class="code">standard-class</span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">monitoring-function <span class="keyword">:initarg</span> <span class="keyword">:monitoring-function</span>
<span class="keyword">:accessor</span> monitoring-function
<span class="keyword">:initform</span> #'log-slot-access</span>)</span></span>)</span></span>)</span> <span class="comment">; default function</span></span></code></pre>
<p>But this won't work for a number of reasons we'll see shortly. Instead we'll
need to handle the initialization ourselves. We'll do it in <code>shared-initialize</code>
so it will happen both when a class is first created and when it's reinitialized
(e.g. after it's redefined):</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> parse-monitoring-function-class-option <span class="paren2">(<span class="code">arguments</span>)</span>
<span class="paren2">(<span class="code">case <span class="paren3">(<span class="code">length arguments</span>)</span>
<span class="paren3">(<span class="code">1 <span class="paren4">(<span class="code">eval <span class="paren5">(<span class="code">first arguments</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">0 #'log-slot-access</span>)</span>
<span class="paren3">(<span class="code">t <span class="paren4">(<span class="code">error <span class="string">&quot;Malformed monitoring-function option.&quot;</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> shared-initialize <span class="keyword">:around</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">class monitored-class</span>)</span> slot-names
&amp;rest initargs
&amp;key monitoring-function &amp;allow-other-keys</span>)</span>
<span class="paren2">(<span class="code">apply #'call-next-method class slot-names
<span class="keyword">:monitoring-function</span> <span class="paren3">(<span class="code">parse-monitoring-function-class-option
monitoring-function</span>)</span>
initargs</span>)</span></span>)</span></span></code></pre>
<p>If the user provided a <code>(:monitoring-function …)</code> class option we evaluate and
use it, otherwise we default to our simple logging function.</p>
<p>There are a couple of things to note here.</p>
<p>First, when <code>defclass</code> gets a class option like <code>(:monitoring-function foo)</code>,
what it <em>actually</em> passes to the <code>(re)initialize-instance</code> methods is the list
<code>(foo)</code>. This allows for class options with more than one argument. In our
case we only ever want a single argument, so we ensure the <code>length</code> of the
argument is <code>0</code> or <code>1</code> and handle the cases individually.</p>
<p>Second, <code>defclass</code> does not evaluate the class option's arguments. If we say
<code>(:monitoring-function (lambda (i s &amp;optional v) (print (list i s v))))</code> what we
get as the initarg will be <code>((lambda (i s &amp;optional v) (print (list i s v))))</code>.
That's a list of a list of three elements, <em>not</em> a list of an actual function
object. If we want the arguments to be evaluated, we have to do it ourselves.
Unfortunately as far as I can tell there's no way to evaluate these arguments
from <code>defclass</code> in their lexical environment — we have to fall back to <code>eval</code>
and the null lexical environment. That means that something like this will not
work:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">flet</span></i> <span class="paren2">(<span class="code"><span class="paren3">(<span class="code">monitor <span class="paren4">(<span class="code">instance slot-name &amp;optional new-value</span>)</span>
</span>)</span></span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">defclass</span></i> foo <span class="paren3">(<span class="code"></span>)</span>
<span class="paren3">(<span class="code">…slots…</span>)</span>
<span class="paren3">(<span class="code"><span class="keyword">:monitoring-function</span> #'monitor</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>I haven't managed to find a way to make this work with <code>defclass</code>. If anyone
knows of a solution, please let me know.</p>
<p>Third, you might notice that we're <code>apply</code>ing with the full <code>initargs</code> list,
which includes the original (unparsed) <code>monitoring-function</code>. But that keyword
argument will be shadowed by the <code>:monitoring-function</code> we add at the beginning,
so there's no need to bother removing it from <code>initargs</code> before we apply (though
it wouldn't hurt to do so). This is another Common Lisp idiom you'll see here
and there when someone wants to override a single keyword argument but preserve
all the rest.</p>
<p>Now we can talk about all the reasons why <code>:initform #'log-slot-access</code> doesn't
magically solve all our problems.</p>
<p>First, the <code>:initform</code> <em>would</em> work properly when you first define a class, but
we still need all the code in <code>shared-initialize</code> to do the <code>eval</code>ing of the
forms the user provides when they <em>don't</em> use it.</p>
<p>Further, suppose a user runs:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> foo <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">some-slot <span class="keyword">:initarg</span> <span class="keyword">:some-slot</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:monitoring-function</span> monitor-foo</span>)</span></span>)</span></span></code></pre>
<p>Then later they <em>remove</em> the <code>:monitoring-function</code> from the <code>defclass</code> and
reevaluate it:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> foo <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">some-slot <span class="keyword">:initarg</span> <span class="keyword">:some-slot</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span></span>)</span></span></code></pre>
<p>What the user (probably) expects here is for the class to have the default
monitoring function. This is what will happen if they start a fresh Lisp image
and load the current code into it. But if we had just used <code>:initform</code>, the
class would already have a value for the <code>monitoring-function</code> slot (the old
function) and since there's no <em>new</em> value being specified, the <code>:initform</code>
would never be used and nothing would get updated, so the class would continue
to use the old monitoring function. The user would have to clean things up
manually by killing the class with <code>(setf (find-class 'foo) nil)</code> and
reevaluating the <code>defclass</code>, or fixing the slot value up manually, or some other
ugly alternative.</p>
<p>With all that out of the way, we can now use a custom <code>monitoring-function</code> to
do whatever we want:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*user-slot-reads*</span> <span class="paren2">(<span class="code">make-hash-table</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*user-slot-writes*</span> <span class="paren2">(<span class="code">make-hash-table</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> track-user-slot-access
<span class="paren2">(<span class="code">instance slot-name &amp;optional <span class="paren3">(<span class="code">new-value nil new-value?</span>)</span></span>)</span>
<span class="paren2">(<span class="code">declare <span class="paren3">(<span class="code">ignore instance new-value</span>)</span></span>)</span>
<span class="paren2">(<span class="code">incf <span class="paren3">(<span class="code">gethash slot-name
<span class="paren4">(<span class="code"><i><span class="symbol">if</span></i> new-value? <span class="special">*user-slot-writes*</span> <span class="special">*user-slot-reads*</span></span>)</span>
0</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> user <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">id <span class="keyword">:initarg</span> <span class="keyword">:id</span></span>)</span>
<span class="paren3">(<span class="code">name <span class="keyword">:initarg</span> <span class="keyword">:name</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:monitoring-function</span> #'track-user-slot-access</span>)</span></span>)</span>
<span class="comment">;; Two writes
</span><span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*u*</span> <span class="paren2">(<span class="code">make-instance 'user <span class="keyword">:id</span> 1 <span class="keyword">:name</span> <span class="string">&quot;sjl&quot;</span></span>)</span></span>)</span>
<span class="comment">;; A read
</span><span class="paren1">(<span class="code">slot-value <span class="special">*u*</span> 'id</span>)</span>
<span class="comment">;; Two more writes
</span><span class="paren1">(<span class="code">setf <span class="paren2">(<span class="code">slot-value <span class="special">*u*</span> 'name</span>)</span> <span class="string">&quot;steve&quot;</span></span>)</span>
<span class="paren1">(<span class="code">setf <span class="paren2">(<span class="code">slot-value <span class="special">*u*</span> 'name</span>)</span> <span class="string">&quot;sjl&quot;</span></span>)</span>
<span class="comment">;; Results
</span><span class="paren1">(<span class="code">alexandria:hash-table-alist <span class="special">*user-slot-reads*</span></span>)</span>
<span class="comment">; =&gt; ((ID . 1))
</span>
<span class="paren1">(<span class="code">alexandria:hash-table-alist <span class="special">*user-slot-writes*</span></span>)</span>
<span class="comment">; =&gt; ((NAME . 3) (ID . 1))</span></span></code></pre>
<h3 id="s3-toy-example-disclaimer"><a href="index.html#s3-toy-example-disclaimer">Toy Example Disclaimer</a></h3>
<p>The <code>monitored-class</code> example we've used so far is pretty small, and there are
a number of other ways we could accomplish the same thing, some of which might
not involve metaclasses at all. This might make my example seem overly
complicated.</p>
<p>I wanted to keep the example small so I can focus on the actual problem I ran
into without getting bogged down in too many irrelevant details about a specific
implementation. If you're bothered by how we're using metaclasses here when
there are other ways to implement this toy example, feel free to implement
a more extensive <code>monitored-class</code> variant as an exercise:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> user <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">id …</span>)</span>
<span class="comment">;; Never monitor this slot:
</span> <span class="paren3">(<span class="code">session-id … <span class="keyword">:monitored</span> nil</span>)</span>
<span class="comment">;; We only care when this slot *changes*:
</span> <span class="paren3">(<span class="code">role … <span class="keyword">:monitored/reads</span> nil</span>)</span>
<span class="comment">;; Names are PII, redact their values before logging:
</span> <span class="paren3">(<span class="code">name … <span class="keyword">:monitored/redact-value</span> t</span>)</span>
<span class="comment">;; Redact the user portion of the email address, logging only the domain:
</span> <span class="paren3">(<span class="code">email … <span class="keyword">:monitored/redact-value</span> #'scrub-email</span>)</span></span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:monitoring-function</span> #'log-to-syslog</span>)</span>
<span class="comment">;; Allow us to turn monitoring on/off globally:
</span> <span class="paren2">(<span class="code"><span class="keyword">:monitor-when</span> #'monitoring-enabled-p</span>)</span></span>)</span></span></code></pre>
<h2 id="s4-the-problem"><a href="index.html#s4-the-problem">The Problem</a></h2>
<p>Let's return to a toy example that will help demonstrate the problem I ran into.
Suppose we have a <code>user</code> class and want to monitor that class to log a warning
if someone ever changes the <code>id</code> of an instance:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> monitor-user <span class="paren2">(<span class="code">instance slot &amp;optional <span class="paren3">(<span class="code">new-value nil new-value?</span>)</span></span>)</span>
<span class="paren2">(<span class="code">when <span class="paren3">(<span class="code">and <span class="paren4">(<span class="code">eql slot 'id</span>)</span> new-value?</span>)</span>
<span class="paren3">(<span class="code">when <span class="paren4">(<span class="code">slot-boundp instance 'id</span>)</span> <span class="comment">; ignore initial setting of the value
</span> <span class="paren4">(<span class="code">format t <span class="string">&quot;WARNING: User ~A is getting a new ID ~A, this is concerning.&quot;</span>
<span class="paren5">(<span class="code">slot-value instance 'id</span>)</span>
new-value</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> user <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">id <span class="keyword">:initarg</span> <span class="keyword">:id</span></span>)</span>
<span class="paren3">(<span class="code">name <span class="keyword">:initarg</span> <span class="keyword">:name</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:monitoring-function</span> #'monitor-user</span>)</span></span>)</span></span></code></pre>
<p>This works as expected:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*u*</span> <span class="paren2">(<span class="code">make-instance 'user <span class="keyword">:id</span> 1 <span class="keyword">:name</span> <span class="string">&quot;sjl&quot;</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code">slot-value <span class="special">*u*</span> 'id</span>)</span>
<span class="comment">; =&gt; 1
</span>
<span class="paren1">(<span class="code">setf <span class="paren2">(<span class="code">slot-value <span class="special">*u*</span> 'id</span>)</span> 999</span>)</span>
<span class="comment">; WARNING: User 1 is getting a new ID 999, this is concerning.
</span><span class="comment">; =&gt; 999
</span>
<span class="paren1">(<span class="code">slot-value <span class="special">*u*</span> 'id</span>)</span>
<span class="comment">; =&gt; 999</span></span></code></pre>
<p>So far, so good. But what happens if we add a subclass of user?</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> paid-user <span class="paren2">(<span class="code">user</span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">plan <span class="keyword">:initarg</span> <span class="keyword">:plan</span> <span class="keyword">:type</span> <span class="paren4">(<span class="code">member <span class="keyword">:bronze</span> <span class="keyword">:silver</span> <span class="keyword">:gold</span></span>)</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*p*</span>
<span class="paren2">(<span class="code">make-instance 'paid-user <span class="keyword">:id</span> 2 <span class="keyword">:name</span> <span class="string">&quot;moneybags&quot;</span> <span class="keyword">:plan</span> <span class="keyword">:gold</span></span>)</span></span>)</span>
<span class="comment">; =&gt; Setting slot ID of #&lt;PAID-USER {100DE55F43}&gt; to 2 at 3870460545.
</span><span class="comment">; =&gt; Setting slot NAME of #&lt;PAID-USER {100DE55F43}&gt; to &quot;moneybags&quot; at 3870460545.
</span><span class="comment">; =&gt; Setting slot PLAN of #&lt;PAID-USER {100DE55F43}&gt; to :GOLD at 3870460545.</span></span></code></pre>
<p>We can already see the problem: we didn't explicitly specify
<code>(:monitoring-function #'monitor-user)</code> in the <code>defclass</code> options, so this class
used the default monitoring function instead of inheriting the monitoring
function from its superclass. This may be what you want in some cases, but for
this case I'd prefer subclasses to inherit their superclass' monitoring function
if they don't explicitly specify one themselves.</p>
<p>When I saw this, my first instinct was to update
<code>parse-monitoring-function-class-option</code> to take the class as an extra option
and use that to look up a superclass monitoring function (if any) to use as the
default instead, which would look something like this:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> monitored-class-p <span class="paren2">(<span class="code">class</span>)</span>
<span class="paren2">(<span class="code">typep class 'monitored-class</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> first-monitored-superclass <span class="paren2">(<span class="code">class</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">superclasses <span class="paren5">(<span class="code">rest <span class="paren6">(<span class="code">c2mop:class-precedence-list class</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">first <span class="paren4">(<span class="code">remove-if-not #'monitored-class-p superclasses</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> parse-monitoring-function-class-option <span class="paren2">(<span class="code">class arguments</span>)</span>
<span class="paren2">(<span class="code">case <span class="paren3">(<span class="code">length arguments</span>)</span>
<span class="paren3">(<span class="code">1 <span class="paren4">(<span class="code">eval <span class="paren5">(<span class="code">first arguments</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">0 <span class="paren4">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren5">(<span class="code"><span class="paren6">(<span class="code">super <span class="paren1">(<span class="code">first-monitored-superclass class</span>)</span></span>)</span></span>)</span>
<span class="comment">;; Inherit the monitoring function from its most specific monitored
</span> <span class="comment">;; superclass, or use the default if there isn't one.
</span> <span class="paren5">(<span class="code"><i><span class="symbol">if</span></i> super
<span class="paren6">(<span class="code">monitoring-function super</span>)</span>
#'log-slot-access</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">t <span class="paren4">(<span class="code">error <span class="string">&quot;Malformed monitoring-function option.&quot;</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> shared-initialize <span class="keyword">:around</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">class monitored-class</span>)</span> slot-names
&amp;rest initargs
&amp;key monitoring-function &amp;allow-other-keys</span>)</span>
<span class="paren2">(<span class="code">apply #'call-next-method class slot-names
<span class="keyword">:monitoring-function</span> <span class="paren3">(<span class="code">parse-monitoring-function-class-option
class monitoring-function</span>)</span>
initargs</span>)</span></span>)</span></span></code></pre>
<p>Unfortunately, if you try to actually run that code you'll discover a few
unpleasant things. First, the class precedence list isn't available the first
time the class is being initialized. So we can't use it in <code>shared-initialize</code>
like this.</p>
<p>Second, I misled you earlier. There's a line <a href="http://metamodular.com/CLOS-MOP/initialization-of-class-metaobjects2.html">deep in the bowels of the
Metaobject protocol</a> that says:</p>
<blockquote>
<p>Portable programs must not define methods on <code>shared-initialize</code>.</p>
</blockquote>
<p>So we can't use <code>shared-initialize</code> as a shortcut <em>at all</em>, and will need to
define separate methods for <code>initialize-instance</code> and <code>reinitialize-instance</code>
after all.</p>
<p>But even worse, if we think ahead a little bit (which I, of course, did not do
when I was figuring all this out), we can see this entire strategy is doomed to
failure from the start. Consider the following series of actions by a user at
a REPL:</p>
<pre><code><span class="code"><span class="comment">;; Create user class, monitor with default function.
</span><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> user <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">id <span class="keyword">:initarg</span> <span class="keyword">:id</span></span>)</span>
<span class="paren3">(<span class="code">name <span class="keyword">:initarg</span> <span class="keyword">:name</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span></span>)</span>
<span class="comment">;; Create paid user class, inherits monitoring function from user.
</span><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> paid-user <span class="paren2">(<span class="code">user</span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">plan <span class="keyword">:initarg</span> <span class="keyword">:plan</span> <span class="keyword">:type</span> <span class="paren4">(<span class="code">member <span class="keyword">:bronze</span> <span class="keyword">:silver</span> <span class="keyword">:gold</span></span>)</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span></span>)</span>
<span class="comment">;; Redefine user class, because actually we want to log
</span><span class="comment">;; monitored slots to Postgres.
</span><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> user <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">id <span class="keyword">:initarg</span> <span class="keyword">:id</span></span>)</span>
<span class="paren3">(<span class="code">name <span class="keyword">:initarg</span> <span class="keyword">:name</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:monitoring-function</span> #'log-slots-to-postgres</span>)</span></span>)</span> <span class="comment">; NEW</span></span></code></pre>
<p>Clearly what should happen here is that the <code>paid-user</code> class should now inherit
the <em>new</em> monitoring function. But the strategy of trying to set the monitoring
function <em>once</em> when a class is initialized or reinitialized falls apart when
you want to support redefinition of superclasses and have their subclasses
inherit changes.</p>
<p>At this point, things are not looking good. We need a new plan.</p>
<h2 id="s5-the-dependent-maintenance-protocol"><a href="index.html#s5-the-dependent-maintenance-protocol">The Dependent Maintenance Protocol</a></h2>
<p>Fortunately, as often happens in Common Lisp, the creators of CLOS and the
Metaobject Protocol had a wonderful amount of foresight and provided a way out
of this problem in the form of the <a href="http://metamodular.com/CLOS-MOP/dependent-maintenance-protocol.html">CLOS Dependent Maintenance Protocol</a>.
From that page:</p>
<blockquote>
<p>It is convenient for portable metaobjects to be able to memoize information
about other metaobjects[…]. Because class […] metaobjects can be
reinitialized[…], a means must be provided to update this memoized
information.</p>
</blockquote>
<p>This is exactly what we need! We want to memoize the monitoring function each
monitored class will use, and we need to keep that up to date when any of the
classes in the inheritance hierarchy are updated.</p>
<p>The full details are laid out in the protocol documentation, but let's step
through an example here to see it in action.</p>
<h3 id="s6-dependency-wrappers"><a href="index.html#s6-dependency-wrappers">Dependency Wrappers</a></h3>
<p>The protocol states:</p>
<blockquote>
<p>To prevent conflicts between two portable programs, or between portable
programs and the implementation, portable code must not register metaobjects
themselves as dependents. Instead, portable programs which need to record
a metaobject as a dependent, should encapsulate that metaobject in some other
kind of object, and record that object as the dependent.</p>
</blockquote>
<p>With this in mind, we'll need to make a small wrapper we can use to store
dependents:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> dependency <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">dependent <span class="keyword">:accessor</span> dependent <span class="keyword">:initarg</span> <span class="keyword">:dep</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>And then we'll make some utility functions to add and remove dependencies
to/from classes, which we'll use shortly:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> dependency= <span class="paren2">(<span class="code">d class</span>)</span>
<span class="string">&quot;Return whether `d` is a dependency on `class`.&quot;</span>
<span class="comment">;; We need to filter out any other dependents other code might have added.
</span> <span class="paren2">(<span class="code">and <span class="paren3">(<span class="code">typep d 'dependency</span>)</span>
<span class="paren3">(<span class="code">eql <span class="paren4">(<span class="code">dependent d</span>)</span> class</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> ensure-dependency <span class="paren2">(<span class="code">superclass class</span>)</span>
<span class="string">&quot;Ensure that `class` is a dependent of `superclass`.&quot;</span>
<span class="paren2">(<span class="code">c2mop:map-dependents superclass
<span class="paren3">(<span class="code"><i><span class="symbol">lambda</span></i> <span class="paren4">(<span class="code">d</span>)</span>
<span class="paren4">(<span class="code">when <span class="paren5">(<span class="code">dependency= d class</span>)</span>
<span class="paren5">(<span class="code"><i><span class="symbol">return-from</span></i> ensure-dependency</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code">c2mop:add-dependent superclass <span class="paren3">(<span class="code">make-instance 'dependency <span class="keyword">:dep</span> class</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> ensure-no-dependency <span class="paren2">(<span class="code">superclass class</span>)</span>
<span class="string">&quot;Ensure that `class` is NOT a dependent of `superclass`.&quot;</span>
<span class="paren2">(<span class="code">c2mop:map-dependents superclass
<span class="paren3">(<span class="code"><i><span class="symbol">lambda</span></i> <span class="paren4">(<span class="code">d</span>)</span>
<span class="paren4">(<span class="code">when <span class="paren5">(<span class="code">dependency= d class</span>)</span>
<span class="paren5">(<span class="code">c2mop:remove-dependent superclass d</span>)</span>
<span class="paren5">(<span class="code"><i><span class="symbol">return-from</span></i> ensure-no-dependency</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>When we define a subclass on a monitored class, e.g. when we ran <code>(defclass
paid-user (user) …)</code> before, we'll need to <code>(ensure-dependency user paid-user)</code>
to tell CLOS that <code>paid-user</code> is dependent on <code>user</code>, and needs to be updated if
<code>user</code> is changed. We also want to make sure to only add the dependency if it
doesn't already exist, to avoid useless work.</p>
<p>But things can get a little trickier than this, because if <code>paid-user</code> is then
redefined to <em>not</em> be a subclass of <code>user</code> any more (unlikely, but possible) we
want to <em>remove</em> that dependency. So we'll need both utility functions for
managing the dependencies.</p>
<h3 id="s7-defining-the-metaclass"><a href="index.html#s7-defining-the-metaclass">Defining the Metaclass</a></h3>
<p>We'll need to update our metaclass to not only store the monitoring function,
but also store what the user <em>specified</em> as the monitoring function, in case we
need to recompute it later. We'll also tell Lisp it's okay for a monitored
class to be a subclass of a standard class, add our <code>slot-value-using-class</code>
methods from before, and define a helper type predicate while we're here:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> monitored-class <span class="paren2">(<span class="code">standard-class</span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">given-monitoring-function
<span class="keyword">:initarg</span> <span class="keyword">:given-monitoring-function</span>
<span class="keyword">:accessor</span> given-monitoring-function</span>)</span>
<span class="paren3">(<span class="code">computed-monitoring-function
<span class="keyword">:initarg</span> <span class="keyword">:computed-monitoring-function</span>
<span class="keyword">:accessor</span> computed-monitoring-function</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> c2mop:validate-superclass
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">class monitored-class</span>)</span> <span class="paren3">(<span class="code">superclass standard-class</span>)</span></span>)</span>
t</span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> c2mop:slot-value-using-class <span class="keyword">:before</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">class monitored-class</span>)</span> instance slot</span>)</span>
<span class="paren2">(<span class="code">funcall <span class="paren3">(<span class="code">computed-monitoring-function class</span>)</span>
instance
<span class="paren3">(<span class="code">c2mop:slot-definition-name slot</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> <span class="paren2">(<span class="code">setf c2mop:slot-value-using-class</span>)</span> <span class="keyword">:before</span>
<span class="paren2">(<span class="code">new-value <span class="paren3">(<span class="code">class monitored-class</span>)</span> instance slot</span>)</span>
<span class="paren2">(<span class="code">funcall <span class="paren3">(<span class="code">computed-monitoring-function class</span>)</span>
instance
<span class="paren3">(<span class="code">c2mop:slot-definition-name slot</span>)</span>
new-value</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> monitored-class-p <span class="paren2">(<span class="code">object</span>)</span>
<span class="paren2">(<span class="code">typep object 'monitored-class</span>)</span></span>)</span></span></code></pre>
<h3 id="s8-computing-slots"><a href="index.html#s8-computing-slots">Computing Slots</a></h3>
<p>We're going to need a function for computing the value of the slot. It will
serve the same role <code>parse-monitoring-function-class-option</code> was serving before.</p>
<p>If we only have one class option like <code>:monitoring-function</code> we could hardcode
it into a function like this:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> recompute-monitoring-function <span class="paren2">(<span class="code">&amp;key class superclasses value value?</span>)</span>
<span class="string">&quot;Set the metaclass' monitoring-function slots to the appropriate value.
If the user provides an explicit value it will be used, otherwise the value
will be inherited from any superclass' value, otherwise the default will be
used.
In any case, the computed value is stored in the `computed-…` slot, and the
original user-given value (if any) is stored in the `given-…` slot so we can
use it later if any superclasses change and we need to recompute this.
&quot;</span>
<span class="comment">;; Only consider monitored superclasses.
</span> <span class="paren2">(<span class="code">setf superclasses <span class="paren3">(<span class="code">remove-if-not #'monitored-class-p superclasses</span>)</span></span>)</span>
<span class="comment">;; We need to store whether the user gave an explicit value for later.
</span> <span class="paren2">(<span class="code"><i><span class="symbol">if</span></i> value?
<span class="paren3">(<span class="code">setf <span class="paren4">(<span class="code">slot-value class 'given-monitoring-function</span>)</span> value</span>)</span>
<span class="paren3">(<span class="code">slot-makunbound class 'given-monitoring-function</span>)</span></span>)</span>
<span class="comment">;; Set the computed value.
</span> <span class="paren2">(<span class="code">setf <span class="paren3">(<span class="code">slot-value class 'computed-monitoring-function</span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">cond</span></i>
<span class="comment">;; If the user gave a value, use it (after checking it's well-formed).
</span> <span class="paren4">(<span class="code">value? <span class="paren5">(<span class="code"><i><span class="symbol">progn</span></i> <span class="paren6">(<span class="code">assert <span class="paren1">(<span class="code">= 1 <span class="paren2">(<span class="code">length value</span>)</span></span>)</span></span>)</span>
<span class="paren6">(<span class="code">eval <span class="paren1">(<span class="code">first value</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="comment">;; Otherwise, if there are any monitored superclasses, use the most
</span> <span class="comment">;; specific one's monitoring function.
</span> <span class="paren4">(<span class="code">superclasses <span class="paren5">(<span class="code">slot-value <span class="paren6">(<span class="code">first superclasses</span>)</span>
'computed-monitoring-function</span>)</span></span>)</span>
<span class="comment">;; Otherwise use the default.
</span> <span class="paren4">(<span class="code">t #'log-slot-access</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>First we clean up the superclass list to only consider relevant superclasses.</p>
<p>Then we store the value the user gave, if any, in the
<code>given-monitoring-function</code> slot of the class. If they <em>didn't</em> specify a value
(e.g. if they <em>removed</em> it and reevaluated the <code>defclass</code>), we make sure to
account for that by <code>slot-makunbound</code>ing the slot to clear out any possible old
value.</p>
<p>Then we compute what the real value should be. If they gave us a value, we
<code>eval</code> it as we talked about earlier and use that. Otherwise we use whatever we
computed for a superclass, if available, otherwise the default.</p>
<p>This is all we need if we've only got one option to deal with, as in our toy
example. In my <em>actual</em> project I have a bunch of these options, and so added
a slightly-tedious layer of abstraction to avoid the very-tedious copy/paste
approach:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> recompute-slot
<span class="paren2">(<span class="code">&amp;key class superclasses computed-slot given-slot value value? <i><span class="symbol">default</span></i></span>)</span>
<span class="string">&quot;Set the metaclass' slots to the appropriate value.
For metaclass slots if the user provides an explicit value it will be used,
otherwise the value will be inherited from any superclass' value, otherwise
the default will be used.
In any case, the computed value is stored in the `computed-…` slot, and the
original user-given value (if any) is stored in the `given-…` slot so we can
use it later if any superclasses change and we need to recompute this.
&quot;</span>
<span class="comment">;; Only consider monitored superclasses.
</span> <span class="paren2">(<span class="code">setf superclasses <span class="paren3">(<span class="code">remove-if-not #'monitored-class-p superclasses</span>)</span></span>)</span>
<span class="comment">;; We need to store whether the user gave an explicit value for later.
</span> <span class="paren2">(<span class="code"><i><span class="symbol">if</span></i> value?
<span class="paren3">(<span class="code">setf <span class="paren4">(<span class="code">slot-value class given-slot</span>)</span> value</span>)</span>
<span class="paren3">(<span class="code">slot-makunbound class given-slot</span>)</span></span>)</span>
<span class="comment">;; Set the actual value to the given value, or the superclass value,
</span> <span class="comment">;; or the default.
</span> <span class="paren2">(<span class="code">setf <span class="paren3">(<span class="code">slot-value class computed-slot</span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">cond</span></i>
<span class="paren4">(<span class="code">value? <span class="paren5">(<span class="code"><i><span class="symbol">progn</span></i> <span class="paren6">(<span class="code">assert <span class="paren1">(<span class="code">= 1 <span class="paren2">(<span class="code">length value</span>)</span></span>)</span></span>)</span>
<span class="paren6">(<span class="code">eval <span class="paren1">(<span class="code">first value</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren4">(<span class="code">superclasses <span class="paren5">(<span class="code">slot-value <span class="paren6">(<span class="code">first superclasses</span>)</span> computed-slot</span>)</span></span>)</span>
<span class="paren4">(<span class="code">t <i><span class="symbol">default</span></i></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> recompute-slots <span class="paren2">(<span class="code">class &amp;key
direct-superclasses
<span class="paren3">(<span class="code">monitoring-function nil monitoring-function?</span>)</span>
&amp;allow-other-keys</span>)</span>
<span class="paren2">(<span class="code">recompute-slot <span class="keyword">:class</span> class
<span class="keyword">:superclasses</span> direct-superclasses
<span class="keyword">:computed-slot</span> 'computed-monitoring-function
<span class="keyword">:given-slot</span> 'given-monitoring-function
<span class="keyword">:value</span> monitoring-function
<span class="keyword">:value?</span> monitoring-function?
<span class="keyword">:default</span> #'log-slot-access</span>)</span></span>)</span></span></code></pre>
<p>Supporting more options is just a matter of adding more calls inside of
<code>recompute-slots</code>. It's not the most exciting code I've ever written, but it
works.</p>
<h3 id="s9-initialization"><a href="index.html#s9-initialization">Initialization</a></h3>
<p>Now we can finally define the <code>initialize-instance</code> and <code>reinitialize-instance</code>
methods on our class. We'll start with <code>initialize-instance</code> (and a helper
function):</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> strip-initargs <span class="paren2">(<span class="code">initargs</span>)</span>
<span class="string">&quot;Remove any monitored-class initargs from `initargs`.
We need to do this because we handle these ourselves before
`call-next-method`, in `recompute-slots`, and if we leave them in
the initarg list then `call-next-method` will explode.
&quot;</span>
<span class="paren2">(<span class="code"><i><span class="symbol">loop</span></i> <span class="keyword">:for</span> <span class="paren3">(<span class="code">initarg value</span>)</span> <span class="keyword">:on</span> initargs <span class="keyword">:by</span> #'cddr
<span class="keyword">:unless</span> <span class="paren3">(<span class="code">member initarg '<span class="paren4">(<span class="code"><span class="keyword">:monitoring-function</span></span>)</span></span>)</span>
<span class="keyword">:append</span> <span class="paren3">(<span class="code">list initarg value</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> initialize-instance <span class="keyword">:around</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">class monitored-class</span>)</span> &amp;rest initargs &amp;key &amp;allow-other-keys</span>)</span>
<span class="paren2">(<span class="code">apply #'recompute-slots class initargs</span>)</span>
<span class="paren2">(<span class="code">apply #'call-next-method class <span class="paren3">(<span class="code">strip-initargs initargs</span>)</span></span>)</span>
<span class="paren2">(<span class="code">dolist <span class="paren3">(<span class="code">superclass <span class="paren4">(<span class="code">c2mop:class-direct-superclasses class</span>)</span></span>)</span>
<span class="paren3">(<span class="code">ensure-dependency superclass class</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>We recompute our special slots, then delegate to <code>call-next-method</code> to handle
everything else, after stripping out our initargs because we've already handled
them.</p>
<p>The only other thing we have to do is plug into the dependent maintenance
protocol, to ensure that this new class is a dependent of all its superclasses.</p>
<p>You might think I'm being wasteful here and we should only add dependencies on
superclasses that are instances of our particular metaclass. For example, if we
have:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> some-other-mixin <span class="paren2">(<span class="code"></span>)</span> <span class="paren2">(<span class="code"></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> user <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code">…slots…</span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> paid-user <span class="paren2">(<span class="code">user some-other-mixin</span>)</span>
<span class="paren2">(<span class="code">…slots…</span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span></span>)</span></span></code></pre>
<p>Then <code>paid-user</code> will be a dependent of both <code>user</code> <em>and</em> <code>some-other-mixin</code>.
This seems unnecessary, because changes in non-monitored superclasses won't have
any effect on our monitoring function computation.</p>
<p>Unfortunately, things are not so simple. If we only add dependencies on
monitored superclasses, this will fall apart in the face of <a href="http://metamodular.com/CLOS-MOP/class-finalization-protocol.html">forward-referenced
superclasses</a>. In case you weren't aware, Common Lisp allows you to define
a subclass before its superclass, as long as all the classes are in place before
you try to actually make an instance of the subclass:</p>
<pre><code><span class="code"><span class="comment">;; Define a subclass.
</span><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> bar <span class="paren2">(<span class="code">foo</span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">b <span class="keyword">:accessor</span> b <span class="keyword">:initarg</span> <span class="keyword">:b</span></span>)</span></span>)</span></span>)</span>
<span class="comment">;; Trying to make an instance now will signal an error.
</span><span class="paren1">(<span class="code">make-instance 'bar <span class="keyword">:a</span> 1 <span class="keyword">:b</span> 2</span>)</span>
<span class="comment">; =&gt; While computing the class precedence list of the class named COMMON-LISP-USER::BAR.
</span><span class="comment">; =&gt; The class named COMMON-LISP-USER::FOO is a forward referenced class.
</span><span class="comment">; =&gt; The class named COMMON-LISP-USER::FOO is a direct superclass of the class named COMMON-LISP-USER::BAR.
</span>
<span class="comment">;; Go ahead and define the superclass.
</span><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> foo <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">a <span class="keyword">:accessor</span> a <span class="keyword">:initarg</span> <span class="keyword">:a</span></span>)</span></span>)</span></span>)</span>
<span class="comment">;; Now we can make an instance of the subclass.
</span><span class="paren1">(<span class="code">make-instance 'bar <span class="keyword">:a</span> 1 <span class="keyword">:b</span> 2</span>)</span>
<span class="comment">; =&gt; #&lt;BAR {101144FFC3}&gt;</span></span></code></pre>
<p>This complicates our lives when we're trying to manage dependents, because we
can't possibly know whether a forward-referenced superclass will eventually be
defined as a monitored class or not. So we'll just take the safe route and add
a dependent to <em>all</em> superclasses. This will result in a little extra work, but
it only happens when a class is being defined or redefined which will happen
relatively infrequently.</p>
<h3 id="s10-reinitialization"><a href="index.html#s10-reinitialization">Reinitialization</a></h3>
<p>We'll also need to define a method on <code>reinitialize-instance</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> reinitialize-instance <span class="keyword">:around</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">class monitored-class</span>)</span> &amp;rest initargs
&amp;key <span class="paren3">(<span class="code">direct-superclasses nil direct-superclasses?</span>)</span>
&amp;allow-other-keys</span>)</span>
<span class="paren2">(<span class="code">apply #'recompute-slots class
<span class="keyword">:direct-superclasses</span> <span class="paren3">(<span class="code"><i><span class="symbol">if</span></i> direct-superclasses?
direct-superclasses
<span class="paren4">(<span class="code">c2mop:class-direct-superclasses class</span>)</span></span>)</span>
initargs</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">before <span class="paren5">(<span class="code">c2mop:class-direct-superclasses class</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">apply #'call-next-method class <span class="paren4">(<span class="code">strip-initargs initargs</span>)</span></span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">let*</span></i> <span class="paren4">(<span class="code"><span class="paren5">(<span class="code">after <span class="paren6">(<span class="code">c2mop:class-direct-superclasses class</span>)</span></span>)</span>
<span class="paren5">(<span class="code">removed <span class="paren6">(<span class="code">set-difference before after</span>)</span></span>)</span>
<span class="paren5">(<span class="code">added <span class="paren6">(<span class="code">set-difference after before</span>)</span></span>)</span></span>)</span>
<span class="paren4">(<span class="code">dolist <span class="paren5">(<span class="code">superclass removed</span>)</span>
<span class="paren5">(<span class="code">ensure-no-dependency superclass class</span>)</span></span>)</span>
<span class="paren4">(<span class="code">dolist <span class="paren5">(<span class="code">superclass added</span>)</span>
<span class="paren5">(<span class="code">ensure-dependency superclass class</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>The overall structure of this method is the same as <code>initialize-instance</code>:</p>
<ol>
<li>Recompute values of our special metaclass slot(s).</li>
<li><code>call-next-method</code> to finish the rest of the (re)initialization.</li>
<li>Ensure our dependencies are correct.</li>
</ol>
<p>But there are a couple of fiddly bits to note.</p>
<p>We may or may not get a new set of direct superclasses, depending on how the
reinitialization happened. We <em>always</em> need that list when we call
<code>recompute-slots</code> though, so we'll grab it ourselves if we don't get it.</p>
<p>We also save the list of direct superclasses before and after we defer to
<code>call-next-method</code> to complete the reinitialization, and then compare the list
before and after to figure out which dependencies we need to add or remove.</p>
<p>With all that out of the way, we're almost done.</p>
<h3 id="s11-dependent-updates"><a href="index.html#s11-dependent-updates">Dependent Updates</a></h3>
<p>Now we can finally tell CLOS to update dependents when a monitored class
changes:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> given-to-initarg <span class="paren2">(<span class="code">class initarg given-slot</span>)</span>
<span class="paren2">(<span class="code">when <span class="paren3">(<span class="code">slot-boundp class given-slot</span>)</span>
<span class="paren3">(<span class="code">list initarg <span class="paren4">(<span class="code">slot-value class given-slot</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> c2mop:update-dependent
<span class="paren2">(<span class="code">updated-class <span class="paren3">(<span class="code">dep dependency</span>)</span> &amp;rest initargs</span>)</span>
<span class="paren2">(<span class="code">declare <span class="paren3">(<span class="code">ignore initargs</span>)</span></span>)</span>
<span class="paren2">(<span class="code">when <span class="paren3">(<span class="code">monitored-class-p updated-class</span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">(<span class="code"><span class="paren5">(<span class="code">dependent-class <span class="paren6">(<span class="code">dependent dep</span>)</span></span>)</span></span>)</span>
<span class="paren4">(<span class="code">apply #'reinitialize-instance dependent-class
<span class="paren5">(<span class="code">append
<span class="paren6">(<span class="code">given-to-initarg dependent-class
<span class="keyword">:given-monitoring-function</span>
'given-monitoring-function</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p><code>update-dependent</code> is the key method here. When a superclass with one of these
dependencies is updated, this method will be called. When that happens, we know
we might need to update the subclasses.</p>
<p>First we check to make sure the class being updated really <em>is</em> a monitored
class (and not something that was forward-referenced but didn't turn out to be
monitored).</p>
<p>Assuming we really are updating a monitored class, we call
<code>reinitialize-instance</code> on the dependent class. We set up the initargs to this
call as if the user had reran the <em>dependent's</em> <code>defclass</code> form (because we're
reinitializing the <em>dependent</em>, after the superclass has changed), to ensure
that the recalculation happens properly. An example might make this clearer:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> user <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code">…slots…</span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:monitoring-function</span> #'log-slot</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> paid-user <span class="paren2">(<span class="code">user</span>)</span>
<span class="paren2">(<span class="code">…slots…</span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> audited-user <span class="paren2">(<span class="code">user</span>)</span>
<span class="paren2">(<span class="code">…slots…</span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:monitoring-function</span> #'audit-slot-to-postgres</span>)</span></span>)</span></span></code></pre>
<p>If we now redefine <code>user</code>, both of its dependencies will be reinitialized.</p>
<p>For <code>paid-user</code> we call <code>(reinitialize-instance paid-user)</code> with no initargs,
because there's no <code>(:monitoring-function …)</code> in the <code>defclass</code> form and thus
its <code>given-monitoring-function</code> slot is unbound.</p>
<p>For <code>audited-user</code> we call <code>(reinitialize-instance audited-user
:monitoring-function '(#'audit-slot-to-postgres))</code>, because the <code>audited-user</code>
class <em>does</em> have a monitoring function that <code>recompute-slots</code> will need.</p>
<h2 id="s12-the-result"><a href="index.html#s12-the-result">The Result</a></h2>
<p>With all that in place, our metaclass is ready for interactive use! First we'll
review the default logging function and create two more:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> log-slot-access <span class="paren2">(<span class="code">instance slot-name &amp;optional <span class="paren3">(<span class="code">new-value nil new-value?</span>)</span></span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">if</span></i> new-value?
<span class="paren3">(<span class="code">format t <span class="string">&quot;Setting slot ~A of ~A to ~S at ~A.~%&quot;</span>
slot-name instance new-value <span class="paren4">(<span class="code">get-universal-time</span>)</span></span>)</span>
<span class="paren3">(<span class="code">format t <span class="string">&quot;Reading slot ~A of ~A at ~A.~%&quot;</span>
slot-name instance <span class="paren4">(<span class="code">get-universal-time</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> loud-slot-access <span class="paren2">(<span class="code">instance slot-name &amp;optional <span class="paren3">(<span class="code">new-value nil new-value?</span>)</span></span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">if</span></i> new-value?
<span class="paren3">(<span class="code">format t <span class="string">&quot;SETTING SLOT ~A OF ~A TO ~S AT ~A.~%&quot;</span>
slot-name instance new-value <span class="paren4">(<span class="code">get-universal-time</span>)</span></span>)</span>
<span class="paren3">(<span class="code">format t <span class="string">&quot;READING SLOT ~A OF ~A AT ~A.~%&quot;</span>
slot-name instance <span class="paren4">(<span class="code">get-universal-time</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> quiet-slot-access <span class="paren2">(<span class="code">instance slot-name &amp;optional <span class="paren3">(<span class="code">new-value nil new-value?</span>)</span></span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">if</span></i> new-value?
<span class="paren3">(<span class="code">format t <span class="string">&quot;~A/~A &lt;- ~S~%&quot;</span> instance slot-name new-value</span>)</span>
<span class="paren3">(<span class="code">format t <span class="string">&quot;&lt;- ~A/~A~%&quot;</span> instance slot-name</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Now we can create a few classes:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> foo <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">x <span class="keyword">:initarg</span> <span class="keyword">:x</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> bar <span class="paren2">(<span class="code">foo</span>)</span>
<span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:monitoring-function</span> #'quiet-slot-access</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> baz <span class="paren2">(<span class="code">foo</span>)</span>
<span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span></span>)</span></span></code></pre>
<ul>
<li><code>foo</code> is the superclass, with the default monitoring function.</li>
<li><code>bar</code> subclasses <code>foo</code> but changes the monitoring function.</li>
<li><code>baz</code> subclasses foo and inherits its monitoring function.</li>
</ul>
<p>And everything should work properly:</p>
<pre><code><span class="code"><span class="comment">;; Foo has the default monitoring function ---------------------
</span><span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*foo-object*</span> <span class="paren2">(<span class="code">make-instance 'foo <span class="keyword">:x</span> 1</span>)</span></span>)</span>
<span class="comment">; =&gt; Setting slot X of #&lt;FOO {101190A513}&gt; to 1 at 3870468582.
</span>
<span class="paren1">(<span class="code">slot-value <span class="special">*foo-object*</span> 'x</span>)</span>
<span class="comment">; =&gt; Reading slot X of #&lt;FOO {101190A513}&gt; at 3870468645.
</span>
<span class="paren1">(<span class="code">setf <span class="paren2">(<span class="code">slot-value <span class="special">*foo-object*</span> 'x</span>)</span> 2</span>)</span>
<span class="comment">; =&gt; Setting slot X of #&lt;FOO {101190A513}&gt; to 2 at 3870468657.
</span>
<span class="comment">;; Bar has the quiet one ---------------------------------------
</span><span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*bar-object*</span> <span class="paren2">(<span class="code">make-instance 'bar <span class="keyword">:x</span> 1</span>)</span></span>)</span>
<span class="comment">; =&gt; #&lt;BAR {101190EE03}&gt;/X &lt;- 1
</span>
<span class="paren1">(<span class="code">slot-value <span class="special">*bar-object*</span> 'x</span>)</span>
<span class="comment">; =&gt; &lt;- #&lt;BAR {101190EE03}&gt;/X
</span>
<span class="paren1">(<span class="code">setf <span class="paren2">(<span class="code">slot-value <span class="special">*bar-object*</span> 'x</span>)</span> 2</span>)</span>
<span class="comment">; =&gt; #&lt;BAR {101190EE03}&gt;/X &lt;- 2
</span>
<span class="comment">;; Baz inherits foo's function ---------------------------------
</span><span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*baz-object*</span> <span class="paren2">(<span class="code">make-instance 'baz <span class="keyword">:x</span> 1</span>)</span></span>)</span>
<span class="comment">; =&gt; Setting slot X of #&lt;BAZ {10119142F3}&gt; to 1 at 3870468733.
</span>
<span class="paren1">(<span class="code">slot-value <span class="special">*baz-object*</span> 'x</span>)</span>
<span class="comment">; =&gt; Reading slot X of #&lt;BAZ {10119142F3}&gt; at 3870468755.
</span>
<span class="paren1">(<span class="code">setf <span class="paren2">(<span class="code">slot-value <span class="special">*baz-object*</span> 'x</span>)</span> 2</span>)</span>
<span class="comment">; =&gt; Setting slot X of #&lt;BAZ {10119142F3}&gt; to 2 at 3870468756.</span></span></code></pre>
<p>And now for the <em>real</em> test. We'll redefine <em>only <code>foo</code></em> to change its function:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defclass</span></i> foo <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">x <span class="keyword">:initarg</span> <span class="keyword">:x</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:metaclass</span> monitored-class</span>)</span>
<span class="paren2">(<span class="code"><span class="keyword">:monitoring-function</span> #'loud-slot-access</span>)</span></span>)</span></span></code></pre>
<p>Now <code>foo</code> slot access will be yelled at us:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*foo-object*</span> <span class="paren2">(<span class="code">make-instance 'foo <span class="keyword">:x</span> 1</span>)</span></span>)</span>
<span class="comment">; =&gt; SETTING SLOT X OF #&lt;FOO {1011998B03}&gt; TO 1 AT 3870469055.</span></span></code></pre>
<p><code>bar</code> hasn't changed, because it has its own explicit function defined:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*bar-object*</span> <span class="paren2">(<span class="code">make-instance 'bar <span class="keyword">:x</span> 1</span>)</span></span>)</span>
<span class="comment">; =&gt; #&lt;BAR {101199A913}&gt;/X &lt;- 1</span></span></code></pre>
<p>But, crucially, <code>baz</code> was automatically updated to use the new function it
inherits from <code>foo</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*baz-object*</span> <span class="paren2">(<span class="code">make-instance 'baz <span class="keyword">:x</span> 1</span>)</span></span>)</span>
<span class="comment">; =&gt; SETTING SLOT X OF #&lt;BAZ {101199C293}&gt; TO 1 AT 3870469072.</span></span></code></pre>
<h2 id="s13-is-it-worth-it"><a href="index.html#s13-is-it-worth-it">Is It Worth It?</a></h2>
<p>That was a lot of work. Why did we bother doing it?</p>
<p>One of the strengths of Common Lisp programming is interactive development.
Lispers are used to redefining anything and everything at will and trusting that
their environments can keep up. Interactivity is baked into the bones of the
language — if we want a metaclass to really feel at home, we need to take the
extra steps to make sure it works well in the face of redefinition.</p>
<p>The designers of Common Lisp and the Metaobject Protocol had a lot of foresight
and provided the tools needed to extend the language without destroying its
interactivity. Unfortunately this is a hard problem, and the tools are not
simple to use. It's almost always <em>possible</em> to do things right, but is often
not <em>easy</em>.</p>
<p>Was it worth doing? For this toy example: probably not. For the project I was
working on when I had to figure this all out: I think it was. For your next
project: you'll need to decide that for yourself. But I, at least, am thankful
that the designers of Common Lisp and CLOS made it <em>possible</em> to do things
right, even if it's not always easy.</p>
</article></main><hr class='main-separator' /><footer><nav><a href='https://github.com/sjl/'>GitHub</a><a href='https://twitter.com/stevelosh/'>Twitter</a><a href='https://instagram.com/thirtytwobirds/'>Instagram</a><a href='https://hg.stevelosh.com/.plan/'>.plan</a></nav></footer></body></html>