<?xml version="1.0"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <channel> <title>TurtleWare</title> <link></link> <atom:link href="/rss.xml" rel="self" type="application/rss+xml" /> <language>en-us</language> <pubDate>Wed, 28 Jan 2026 09:15:04 +0100</pubDate> <item> <title>McCLIM and 7GUIs - Part 1: The Counter</title> <link>/posts/McCLIM-and-7GUIs---Part-1-The-Counter.html</link> <pubDate>2026-01-26</pubDate> <author>Daniel Kochmański</author> <guid isPermaLink="true">/posts/McCLIM-and-7GUIs---Part-1-The-Counter.html</guid> <category><![CDATA[ lisp ]]></category><category><![CDATA[ clim ]]></category><category><![CDATA[ mcclim ]]></category><category><![CDATA[ gui ]]></category><category><![CDATA[ tutorial ]]></category> <description><![CDATA[ <h1>Table of Contents</h1>

<ol>
<li><a href="#org1c64900" >Version 1: Using Gadgets and Layouts</a></li>
<li><a href="#org75eb118" >Version 2: Using the CLIM Command Loop</a></li>
<li><a href="#org2717fde" >Conclusion</a></li>
</ol>

<p>For the last two months I've been polishing the upcoming release of
<a href="https://mcclim.common-lisp.dev/main.html" >McCLIM</a>. The most notable change is
the rewriting of the input editing and accepting-values abstractions. As it
happens, I got tired of it, so as a breather I've decided to tackle something I
had in mind for some time to improve the McCLIM manual &#x2013; namely the
<a href="https://eugenkiss.github.io/7guis/" >7GUIs: A GUI Programming Benchmark</a>.</p>

<p>This challenge presents seven distinct tasks commonly found in graphical
interface requirements. In this post I'll address the first challenge - The
Counter. It is a fairly easy task, a warm-up of sorts. The description states:</p>

<blockquote>
<p>Challenge: Understanding the basic ideas of a language/toolkit.</p>

<p>The task is to build a frame containing a label or read-only textfield T and a
button B. Initially, the value in T is “0” and each click of B increases the
value in T by one.</p>

<p>Counter serves as a gentle introduction to the basics of the language, paradigm
and toolkit for one of the simplest GUI applications imaginable. Thus, Counter
reveals the required scaffolding and how the very basic features work together
to build a GUI application. A good solution will have almost no scaffolding.</p>
</blockquote>

<p>In this first post, to make things more interesting, I'll solve it in two ways:</p>

<ul>
<li>using contemporary abstractions like layouts and gadgets</li>
<li>using CLIM-specific abstractions like presentations and translators</li>
</ul>

<p>In CLIM it is possible to mix both paradigms for defining graphical interfaces.
Layouts and gadgets are predefined components that are easy to use, while using
application streams enables a high degree of flexibility and composability.</p>

<p>First, we define a package shared by both versions:</p>

<pre><code>(eval-when (:compile-toplevel :load-toplevel :execute)
  (unless (member :mcclim *features*)
    (ql:quickload &quot;mcclim&quot;)))

(defpackage &quot;EU.TURTLEWARE.7GUIS/TASK1&quot;
  (:use  &quot;CLIM-LISP&quot; &quot;CLIM&quot; &quot;CLIM-EXTENSIONS&quot;)
  (:export &quot;COUNTER-V1&quot; &quot;COUNTER-V2&quot;))
(in-package &quot;EU.TURTLEWARE.7GUIS/TASK1&quot;)
</code></pre>

<p>Note that &quot;CLIM-EXTENSIONS&quot; package is McCLIM-specific.</p>

<p><a id="org1c64900"></a></p>

<h1>Version 1: Using Gadgets and Layouts</h1>

<p>Assuming that we are interested only in the functionality and we are willing to
ignore the visual aspect of the program, the definition will look like this:</p>

<pre><code>(define-application-frame counter-v1 ()
  ((value :initform 0 :accessor value))
  (:panes
   ;;      v type v initarg
   (tfield :label :label (princ-to-string (value *application-frame*))
                  :background +white+)
   (button :push-button :label &quot;Count&quot;
                        :activate-callback (lambda (gadget)
                                             (declare (ignore gadget))
                                             (with-application-frame (frame)
                                               (incf (value frame))
                                               (setf (label-pane-label (find-pane-named frame 'tfield))
                                                     (princ-to-string (value frame)))))))
  (:layouts (default (vertically () tfield button))))

;;; Start the application (if not already running).
;; (find-application-frame 'counter-v1)
</code></pre>

<p><img src="/static/images/7guis-task1-version-1-variant-1.png" alt="" /></p>

<p>The macro <code>define-application-frame</code> is like <code>defclass</code> with additional clauses.
In our program we store the current value as a slot with an accessor.</p>

<p>The clause <code>:panes</code> is responsible for defining named panes (sub-windows). The
first element is the pane name, then we specify its type, and finally we specify
initargs for it. Panes are created in a dynamic context where the application
frame is already bound to <code>*application-frame*</code>, so we can use it there.</p>

<p>The clause <code>:layouts</code> allows us to arrange panes on the screen. There may be
multiple layouts that can be changed at runtime, but we define only one. The
macro <code>vertically</code> creates another (anonymous) pane that arranges one gadget
below another.</p>

<p>Gadgets in CLIM operate directly on top of the event loop. When the pointer
button is pressed, it is handled by activating the callback, that updates the
frame's value and the label. Effects are visible immediately.</p>

<p>Now if we want the demo to look nicer, all we need to do is to fiddle a bit with
<code>spacing</code> and <code>bordering</code> in the <code>:layouts</code> section:</p>

<pre><code>(define-application-frame counter-v1 ()
  ((value :initform 0 :accessor value))
  (:panes
   (tfield :label :label (princ-to-string (value *application-frame*))
                  :background +white+)
   (button :push-button :label &quot;Count&quot;
                        :activate-callback (lambda (gadget)
                                             (declare (ignore gadget))
                                             (with-application-frame (frame)
                                               (incf (value frame))
                                               (setf (label-pane-label (find-pane-named frame 'tfield))
                                                     (princ-to-string (value frame)))))))
  (:layouts (default
             (spacing (:thickness 10)
              (horizontally ()
                (100
                 (bordering (:thickness 1 :background +black+)
                   (spacing (:thickness 4 :background +white+) tfield)))
                15
                (100 button))))))

;;; Start the application (if not already running).
;; (find-application-frame 'counter-v1)
</code></pre>

<p><img src="/static/images/7guis-task1-version-1-variant-2.png" alt="" /></p>

<p>This gives us a layout that is roughly similar to the example presented on the
7GUIs page.</p>

<p><a id="org75eb118"></a></p>

<h1>Version 2: Using the CLIM Command Loop</h1>

<p>Unlike gadgets, stream panes in CLIM operate on top of the command loop. A
single command may span multiple events after which we redisplay the stream to
reflect the new state of the model. This is closer to the interaction type found
in the command line interfaces:</p>

<pre><code>  (define-application-frame counter-v2 ()
    ((value :initform 0 :accessor value))
    (:pane :application
     :display-function (lambda (frame stream)
                         (format stream &quot;~d&quot; (value frame)))))

  (define-counter-v2-command (com-incf-value :name &quot;Count&quot; :menu t)
      ()
    (with-application-frame (frame)
      (incf (value frame))))

;; (find-application-frame 'counter-v2)
</code></pre>

<p><img src="/static/images/7guis-task1-version-2-variant-1.png" alt="" /></p>

<p>Here we've used <code>:pane</code> option this is a syntactic sugar for when we have only
one named pane. Skipping <code>:layouts</code> clause means that named panes will be
stacked vertically one below another.</p>

<p>Defining the application frame defines a command-defining macro. When we define
a command with <code>define-counter-v2-command</code>, then this command will be inserted
into a command table associated with the frame. Passing the option <code>:menu t</code>
causes the command to be available in the frame menu as a top-level entry.</p>

<p>After the command is executed (in this case it modifies the counter value), the
application pane is redisplayed; that is a display function is called, and its
output is captured. In more demanding scenarios it is possible to refine both
the time of redisplay and the scope of changes.</p>

<p>Now we want the demo to look nicer and to have a button counterpart placed
beside the counter value, to resemble the example more:</p>

<pre><code>(define-presentation-type counter-button ())

(define-application-frame counter-v2 ()
  ((value :initform 0 :accessor value))
  (:menu-bar nil)
  (:pane :application
   :width 250 :height 32
   :borders nil :scroll-bars nil
   :end-of-line-action :allow
   :display-function (lambda (frame stream)
                       (formatting-item-list (stream :n-columns 2)
                         (formatting-cell (stream :min-width 100 :min-height 32)
                           (format stream &quot;~d&quot; (value frame)))
                         (formatting-cell (stream :min-width 100 :min-height 32)
                           (with-output-as-presentation (stream nil 'counter-button :single-box t)
                             (surrounding-output-with-border (stream :padding-x 20 :padding-y 0
                                                                     :filled t :ink +light-grey+)
                               (format stream &quot;Count&quot;))))))))

(define-counter-v2-command (com-incf-value :name &quot;Count&quot; :menu t)
    ()
  (with-application-frame (frame)
    (incf (value frame))))

(define-presentation-to-command-translator act-incf-value
    (counter-button com-incf-value counter-v2)
    (object)
  `())

;; (find-application-frame 'counter-v2)
</code></pre>

<p><img src="/static/images/7guis-task1-version-2-variant-2.png" alt="" /></p>

<p>The main addition is the definition of a new presentation type <code>counter-button</code>.
This faux button is printed inside a cell and surrounded with a background.
Later we define a translator that converts clicks on the counter button to the
<code>com-incf-value</code> command. The translator body returns arguments for the command.</p>

<p>Presenting an object on the stream associates a semantic meaning with the
output. We can now extend the application with new gestures (names <code>:scroll-up</code>
and <code>:scroll-down</code> are McCLIM-specific):</p>

<pre><code>(define-counter-v2-command (com-scroll-value :name &quot;Increment&quot;)
    ((count 'integer))
  (with-application-frame (frame)
    (if (plusp count)
        (incf (value frame) count)
        (decf (value frame) (- count)))))

(define-presentation-to-command-translator act-scroll-up-value
    (counter-button com-scroll-value counter-v2 :gesture :scroll-up)
    (object)
  `(10))

(define-presentation-to-command-translator act-scroll-dn-value
    (counter-button com-scroll-value counter-v2 :gesture :scroll-down)
    (object)
  `(-10))

(define-presentation-action act-popup-value
    (counter-button nil counter-v2 :gesture :describe)
    (object frame)
  (notify-user frame (format nil &quot;Current value: ~a&quot; (value frame))))
</code></pre>

<p>A difference between presentation to command translators and presentation
actions is that the latter does not automatically progress the command loop.
Actions are often used for side effects, help, inspection etc.</p>

<p><a id="org2717fde"></a></p>

<h1>Conclusion</h1>

<p>In this short post we've solved the first task from the 7GUIs challenge. We've
used two techniques available in CLIM &#x2013; using layouts and gadgets, and using
display and command tables. Both techniques can be combined, but differences are
visible at a glance:</p>

<ul>
<li>gadgets provide easy and reusable components for rudimentary interactions</li>
<li>streams provide extensible and reusable abstractions for semantic interactions</li>
</ul>

<p>This post only scratched the capabilities of the latter, but the second version
demonstrates why the command loop and presentations scale better than
gadget-only solutions.</p>

<p>Following tasks have gradually increasing level of difficulty that will help us
to emphasize how useful are presentations and commands when we want to write
maintainable applications with reusable user-defined graphical metaphors.</p>
 ]]></description> </item><item> <title>Common Lisp and WebAssembly</title> <link>/posts/Common-Lisp-and-WebAssembly.html</link> <pubDate>2025-11-28</pubDate> <author>Daniel Kochmański</author> <guid isPermaLink="true">/posts/Common-Lisp-and-WebAssembly.html</guid> <category><![CDATA[ lisp ]]></category><category><![CDATA[ webassembly ]]></category> <description><![CDATA[ <h1>Table of Contents</h1>

<ol>
<li><a href="#org91882bd" >Building ECL</a></li>
<li><a href="#org69f465e" >Building WECL</a></li>
<li><a href="#orgb48e043" >Building user programs</a></li>
<li><a href="#org2218eab" >Extending ASDF</a></li>
<li><a href="#org724c21a" >Funding</a></li>
</ol>

<p>Using Common Lisp in WASM enabled runtimes is a new frontier for the Common Lisp
ecosystem. In the previous post <a href="https://turtleware.eu/posts/Using-Common-Lisp-from-inside-the-Browser.html" >Using Common Lisp from inside the Browser</a> I've
discussed how to embed Common Lisp scripts directly on the website, discussed
the foreign function interface to JavaScript and SLIME port called LIME allowing
the user to connect with a local Emacs instance.</p>

<p>This post will serve as a tutorial that describes how to build WECL and how to
cross-compile programs to WASM runtime. Without further ado, let's dig in.</p>

<p><a id="org91882bd"></a></p>

<h1>Building ECL</h1>

<p>To compile ECL targeting WASM we first build the host version and then we use it
to cross-compile it for the target architecture.</p>

<pre><code>git clone https://gitlab.com/embeddable-common-lisp/ecl.git
cd ecl
export ECL_SRC=`pwd`
export ECL_HOST=${ECL_SRC}/ecl-host
./configure --prefix=${ECL_HOST} &amp;&amp; make -j32 &amp;&amp; make install
</code></pre>

<p>Currently ECL uses <a href="https://emscripten.org/docs/tools_reference/emsdk.html" >Emscripten SDK</a> that implements required target primitives
like libc. In the meantime, I'm also porting ECL to <a href="https://wasi.dev/" >WASI</a>, but it is not ready
yet.  In any case we need to install and activate <code>emsdk</code>:</p>

<pre><code>git clone https://github.com/emscripten-core/emsdk.git
pushd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
popd
</code></pre>

<p>Finally it is time to build the target version of ECL. A flag <code>--disable-shared</code>
is optional, but keep in mind that cross-compilation of user programs is a new
feature and it is still taking shape. Most notably some nuances with compiling
systems from <code>.asd</code> files may differ depending on the flag used here.</p>

<pre><code>make distclean # removes build/ directory
export ECL_WASM=${ECL_SRC}/ecl-wasm
export ECL_TO_RUN=${ECL_HOST}/bin/ecl
emconfigure ./configure --host=wasm32-unknown-emscripten --build=x86_64-pc-linux-gnu \
            --with-cross-config=${ECL_SRC}/src/util/wasm32-unknown-emscripten.cross_config \
            --prefix=${ECL_WASM} --disable-shared --with-tcp=no --with-cmp=no

emmake make -j32 &amp;&amp; emmake make install

# some files need to be copied manually
cp build/bin/ecl.js build/bin/ecl.wasm ${ECL_WASM}
</code></pre>

<p>Running from a browser requires us to host the file. To spin Common Lisp web
server on the spot, we can use one of our scripts (that assume that <code>quicklisp</code>
is installed to download <code>hunchentoot</code>).</p>

<pre><code>export WEBSERVER=${ECL_SRC}/src/util/webserver.lisp
${ECL_TO_RUN} --load $WEBSERVER
# After the server is loaded run:
# firefox localhost:8888/ecl-wasm/ecl.html
</code></pre>

<p>Running from <code>node</code> is more straightforward from the console perspective, but
there is one caveat: read operations are not blocking, so if we try to run a
default REPL we'll have many nested I/O errors because stdin returns EOF.
Running in batch mode works fine though:</p>

<pre><code>node ecl-wasm/ecl.js --eval '(format t &quot;Hello world!~%&quot;)' --eval '(quit)'
warning: unsupported syscall: __syscall_prlimit64
Hello world!
program exited (with status: 0), but keepRuntimeAlive() is set (counter=0) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)
</code></pre>

<p>The produced wasm is not suitable for running in other runtimes, because
Emscripten requires <a href="https://github.com/WasmEdge/WasmEdge/issues/2335" >additional functions</a> to <a href="https://emscripten.org/docs/porting/setjmp-longjmp.html" >emulate setjmp</a>. For example:</p>

<pre><code>wasmedge ecl-wasm/ecl.wasm
[2025-11-21 13:34:54.943] [error] instantiation failed: unknown import, Code: 0x62
[2025-11-21 13:34:54.943] [error]     When linking module: &quot;env&quot; , function name: &quot;invoke_iii&quot;
[2025-11-21 13:34:54.943] [error]     At AST node: import description
[2025-11-21 13:34:54.943] [error]     This may be the import of host environment like JavaScript or Golang. Please check that you've registered the necessary host modules from the host programming language.
[2025-11-21 13:34:54.943] [error]     At AST node: import section
[2025-11-21 13:34:54.943] [error]     At AST node: module
</code></pre>

<p><a id="org69f465e"></a></p>

<h1>Building WECL</h1>

<p>The previous step allowed us to run vanilla ECL. Now we are going to use
artifacts created during the compilation to create an application that skips
boilerplate provided by vanilla Emscripten and includes Common Lisp code for
easier development - FFI to JavaScript, windowing abstraction, support for
&lt;script type='common-lisp'&gt;, Emacs connectivity and in-browser REPL support.</p>

<p>First we need to clone the WECL repository:</p>

<pre><code>fossil clone https://fossil.turtleware.eu/wecl
cd wecl
</code></pre>

<p>Then we need to copy over compilation artifacts and my SLIME fork (<a href="https://github.com/slime/slime/pull/879" >pull request</a>)
to the <code>Code</code> directory:</p>

<pre><code>pushd Code
cp -r ${ECL_WASM} wasm-ecl
git clone https://github.com/dkochmanski/slime.git
popd
</code></pre>

<p>Finally we can build and start the application:</p>

<pre><code>./make.sh build
./make.sh serve
</code></pre>

<p>If you want to connect to Emacs, then open the file <code>App/lime.el</code> (it depends on
<code>slime</code> and <code>websocket</code> packages), evaluate the buffer and call the function
<code>(lime-net-listen &quot;localhost&quot; 8889)</code>. Then open a browser at
<a href="http://localhost:8888/slug.html">http://localhost:8888/slug.html</a> and click &quot;Connect&quot;. A new REPL should pop up
in your Emacs instance.</p>

<p>It is time to talk a bit about contents of the <code>wecl</code> repository and how the
instance is bootstrapped. These things are still under development, so details
may change in the future.</p>

<ol>
<li>Compile <code>wecl.wasm</code> and its loader <code>wecl.js</code></li>
</ol>

<p>We've already built the biggest part, that is ECL itself. Now we link
<code>libecl.a</code>, <code>libeclgc.a</code> and <code>libeclgmp.a</code> with the file <code>Code/wecl.c</code> that
calls <code>cl_boot</code> when the program is started. This is no different from the
ordinary embedding procedure of ECL.</p>

<p>The file <code>wecl.c</code> defines additionally supporting functions for JavaScript
interoperation that allow us to call JavaScript and keeping track of shared
objects. These functions are exported so that they are available in CL env.
Moreover it loads a few lisp files:</p>

<ul>
<li><strong>Code/packages.lisp:</strong> package where JS interop functions reside</li>
<li><strong>Code/utilities.lisp:</strong> early utilities used in the codebase (i.e <code>when-let</code>)</li>
<li><strong>Code/wecl.lisp:</strong> JS-FFI, object registry and a stream to wrap <code>console.log</code></li>
<li><strong>Code/jsapi/*.lisp:</strong> JS bindings (operators, classes, &#x2026;)</li>
<li><strong>Code/script-loader.lisp:</strong> loading Common Lisp scripts directly in HTML</li>
</ul>

<p>After that the function returns. It is the user responsibility to start the
program logic in one of scripts loaded by the the script loader. There are a few
examples of this:</p>

<ul>
<li><strong>main.html:</strong> loads a repl and another xterm console (external dependencies)</li>
<li><strong>easy.html:</strong> showcase how to interleave JavaScript and Common Lisp in gadgets</li>
<li><strong>slug.html:</strong> push button that connects to the lime.el instance on localhost</li>
</ul>

<p>The only requirement for the website to use ECL is to include two scripts in its
header. <code>boot.js</code> configures the runtime loader and <code>wecl.js</code> loads <code>wasm</code> file:</p>

<pre><code>&lt;!doctype html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Web Embeddable Common Lisp&lt;/title&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;boot.js&quot;&gt;&lt;/script&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;wecl.js&quot;&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;script type=&quot;text/common-lisp&quot;&gt;
      (loop for i from 0 below 3
            for p = (|createElement| &quot;document&quot; &quot;p&quot;)
            do (setf (|innerText| p) (format nil &quot;Hello world ~a!&quot; i))
               (|appendChild| &quot;document.body&quot; p))
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>I've chosen to use unmodified names of JS operators in bindings to make looking
them up easier. One can use an utility <code>lispify-name</code> to have lispy bindings:</p>

<pre><code>(macrolet ((lispify-operator (name)
             `(defalias ,(lispify-name name) ,name))
           (lispify-accessor (name)
             (let ((lisp-name (lispify-name name)))
               `(progn
                  (defalias ,lisp-name ,name)
                  (defalias (setf ,lisp-name) (setf ,name))))))
  (lispify-operator |createElement|)    ;create-element
  (lispify-operator |appendChild|)      ;append-child
  (lispify-operator |removeChild|)      ;remove-child
  (lispify-operator |replaceChildren|)  ;replace-children
  (lispify-operator |addEventListener|) ;add-event-listener
  (lispify-accessor |innerText|)        ;inner-text
  (lispify-accessor |textContent|)      ;text-content
  (lispify-operator |setAttribute|)     ;set-attribute
  (lispify-operator |getAttribute|))    ;get-attribute
</code></pre>

<p>Note that scripts may be modified without recompiling WECL. On the other hand
files that are loaded at startup (along with swank source code) are embedded in
the <code>wasm</code> file. For now they are loaded at startup, but they may be compiled in
the future if there is such need.</p>

<p>When using WECL in the browser, functions like <code>compile-file</code> and <code>compile</code> are
available and they defer compilation to the bytecodes compiler. The bytecodes
compiler in ECL is very fast, but produces unoptimized bytecode because it is a
one-pass compiler. When performance matters, it is necessary to use compile on
the host to an object file or to a static library and link it against WECL in
file <code>make.sh</code> &#x2013; recompilation of <code>wecl.wasm</code> is necessary.</p>

<p><a id="orgb48e043"></a></p>

<h1>Building user programs</h1>

<p>Recently Marius Gerbershagen improved cross-compilation support for user
programs from the host implementation using the same toolchain that builds ECL.
Compiling files simple: use <code>target-info.lisp</code> file installed along with the
cross-compiled ECL as an argument to <code>with-compilation-unit</code>:</p>

<pre><code>;;; test-file-1.lisp
(in-package &quot;CL-USER&quot;)
(defmacro twice (&amp;body body) `(progn ,@body ,@body))

;;; test-file-1.lisp
(in-package &quot;CL-USER&quot;)
(defun bam (x) (twice (format t &quot;Hello world ~a~%&quot; (incf x))))

(defvar *target*
  (c:read-target-info &quot;/path/to/ecl-wasm/target-info.lsp&quot;))

(with-compilation-unit (:target *target*)
  (compile-file &quot;test-file-1.lisp&quot; :system-p t :load t)
  (compile-file &quot;test-file-2.lisp&quot; :system-p t)
  (c:build-static-library &quot;test-library&quot;
                          :lisp-files '(&quot;test-file-1.o&quot; &quot;test-file-2.o&quot;)
                          :init-name &quot;init_test&quot;))
</code></pre>

<p>This will produce a file <code>libtest-library.a</code>. To use the library in WECL we
should include it in the emcc invocation in <code>make.sh</code> and call the function
<code>init_test</code> in <code>Code/wecl.c</code> before <code>script-loader.lisp</code> is loaded:</p>

<pre><code>/* Initialize your libraries here, so they can be used in user scripts. */
extern void init_test(cl_object);
ecl_init_module(NULL, init_test);
</code></pre>

<p>Note that we've passed the argument <code>:load</code> to <code>compile-file</code> &#x2013; it ensures that
after the file is compiled, we load it (in our case - its source code) using the
target runtime <code>*features*</code> value. During cross-compilation ECL includes also a
feature <code>:cross</code>. Loading the first file is necessary to define a macro that is
used in the second file. Now if we open REPL in the browser:</p>

<pre><code>&gt; #'lispify-name
#&lt;bytecompiled-function LISPIFY-NAME 0x9f7690&gt;
&gt; #'cl-user::bam
#&lt;compiled-function COMMON-LISP-USER::BAM 0x869d20&gt;
&gt; (cl-user::bam 3)
Hello world 4
Hello world 5
</code></pre>

<p><a id="org2218eab"></a></p>

<h1>Extending ASDF</h1>

<p>The approach for cross-compiling in the previous section is the API provided by
ECL. It may be a bit crude for everyday work, especially when we work with a
complex dependency tree. In this section we'll write an extension to ASDF that
allows us to compile entire system with its dependencies into a static library.</p>

<p>First let's define a package and add configure variables:</p>

<pre><code>(defpackage &quot;ASDF-ECL/CC&quot;
  (:use &quot;CL&quot; &quot;ASDF&quot;)
  (:export &quot;CROSS-COMPILE&quot; &quot;CROSS-COMPILE-PLAN&quot; &quot;CLEAR-CC-CACHE&quot;))
(in-package &quot;ASDF-ECL/CC&quot;)

(defvar *host-target*
  (c::get-target-info))

#+(or)
(defvar *wasm-target*
  (c:read-target-info &quot;/path/to/ecl-wasm/target-info.lsp&quot;))

(defparameter *cc-target* *host-target*)
(defparameter *cc-cache-dir* #P&quot;/tmp/ecl-cc-cache/&quot;)
</code></pre>

<p>ASDF operates in two passes &#x2013; first it computes the operation plan and then it
performs it. To help with specifying dependencies ASDF provides five mixins:</p>

<ul>
<li><p><strong>DOWNWARD-OPERATION:</strong> before operating on the component, perform an operation
on children - i.e loading the system requires loading all its components.</p></li>
<li><p><strong>UPWARD-OPERATION:</strong> before operating on the component, perform an operation on
parent - i.e invalidating the cache requires invalidating cache of parent.</p></li>
<li><p><strong>SIDEWAY-OPERATION:</strong> before operating on the component, perform the operation
on all component dependencies - i.e load components that we depend on</p></li>
<li><p><strong>SELFWARD-OPERATION:</strong> before operating on the component, perform operations on
itself - i.e compile the component before loading it</p></li>
<li><p><strong>NON-PROPAGATING-OPERATION:</strong> a standalone operation with no dependencies</p></li>
</ul>

<p>Cross-compilation requires us to produce object file from each source file of
the target system and its dependencies. We will achieve that by defining two
operations: <code>cross-object-op</code> for producing object files from lisp source code
and <code>cross-compile-op</code> for producing static libraries from objects:</p>

<pre><code>(defclass cross-object-op (downward-operation) ())

(defmethod downward-operation ((self cross-object-op))
  'cross-object-op)

;;; Ignore all files that are not CL-SOURCE-FILE.
(defmethod perform ((o cross-object-op) (c t)))

(defmethod perform ((o cross-object-op) (c cl-source-file))
  (let ((input-file (component-pathname c))
        (output-file (output-file o c)))
    (multiple-value-bind (output warnings-p failure-p)
        (compile-file input-file :system-p t :output-file output-file)
      (uiop:check-lisp-compile-results output warnings-p failure-p
                                       &quot;~/asdf-action::format-action/&quot;
                                       (list (cons o c))))))

(defclass cross-compile-op (sideway-operation downward-operation)
  ())

(defmethod perform ((self cross-compile-op) (c system))
  (let* ((system-name (primary-system-name c))
         (inputs (input-files self c))
         (output (output-file self c))
         (init-name (format nil &quot;init_lib_~a&quot;
                            (substitute #\_ nil system-name
                                        :test (lambda (x y)
                                                (declare (ignore x))
                                                (not (alpha-char-p y)))))))
    (c:build-static-library output :lisp-files inputs
                                   :init-name init-name)))

(defmethod sideway-operation ((self cross-compile-op))
  'cross-compile-op)

(defmethod downward-operation ((self cross-compile-op))
  'cross-object-op)
</code></pre>

<p>We can confirm that the plan is computed correctly by running it on a system
with many transient dependencies:</p>

<pre><code>(defun debug-plan (system)
  (format *debug-io* &quot;-- Plan for ~s -----------------~%&quot; system)
  (map nil (lambda (a)
             (format *debug-io* &quot;~24a: ~a~%&quot; (car a) (cdr a)))
       (asdf::plan-actions
        (make-plan 'sequential-plan 'cross-compile-op system))))

(debug-plan &quot;mcclim&quot;)
</code></pre>

<p>In Common Lisp the compilation of subsequent files often depends on previous
definitions. That means that we need to load files. Loading files compiled for
another architecture is not an option. Moreover:</p>

<ul>
<li>some systems will have different dependencies based on <strong>features</strong></li>
<li>code may behave differently depending on the evaluation environment</li>
<li>compilation may require either host or target semantics for cross-compilation</li>
</ul>

<p>There is no general solution except from full target emulation or the client
code being fully aware that it is being cross compiled. That said, surprisingly
many Common Lisp programs can be cross-compiled without many issues.</p>

<p>In any case we need to be able to load source code while it is being compiled.
Depending on the actual code we may want to specify the host or the target
features, load the source code directly or first compile it, etc. To allow user
choosing the load strategy we define an operation <code>cross-load-op</code>:</p>

<pre><code>(defparameter *cc-load-type* :minimal)
(defvar *cc-last-load* :minimal)

(defclass cross-load-op (non-propagating-operation) ())

(defmethod operation-done-p ((o cross-load-op) (c system))
  (and (component-loaded-p c)
       (eql *cc-last-load* *cc-load-type*)))

;;; :FORCE :ALL is excessive. We should store the compilation strategy flag as a
;;; compilation artifact and compare it with *CC-LOAD-TYPE*.
(defmethod perform ((o cross-load-op) (c system))
  (setf *cc-last-load* *cc-load-type*)
  (ecase *cc-load-type*
    (:emulate
     (error &quot;Do you still believe in Santa Claus?&quot;))
    (:default
     (operate 'load-op c))
    (:minimal
     (ext:install-bytecodes-compiler)
     (operate 'load-op c)
     (ext:install-c-compiler))
    (:ccmp-host
     (with-compilation-unit (:target *host-target*)
       (operate 'load-op c :force :all)))
    (:bcmp-host
     (with-compilation-unit (:target *host-target*)
       (ext:install-bytecodes-compiler)
       (operate 'load-op c :force :all)
       (ext:install-c-compiler)))
    (:bcmp-target
     (with-compilation-unit (:target *cc-target*)
       (ext:install-bytecodes-compiler)
       (operate 'load-op c :force :all)
       (ext:install-c-compiler)))
    (:load-host
     (with-compilation-unit (:target *host-target*)
       (operate 'load-source-op c :force :all)))
    (:load-target
     (with-compilation-unit (:target *cc-target*)
       (operate 'load-source-op c :force :all)))))
</code></pre>

<p>To estabilish a cross-compilation dynamic context suitable for ASDF operations
we'll define a new macro <code>WITH-ASDF-COMPILATION-UNIT</code>. It modifies the cache
directory, injects features that are commonly expected by various systems, and
configures the ECL compiler. That macro is used while the</p>

<pre><code>;;; KLUDGE some system definitions test that *FEATURES* contains this or that
;;; variant of :ASDF* and bark otherwise.
;;;
;;; KLUDGE systems may have DEFSYSTEM-DEPENDS-ON that causes LOAD-ASD to try to
;;; load the system -- we need to modify *LOAD-SYSTEM-OPERATION* for that. Not
;;; to be conflated with CROSS-LOAD-UP.
;;; 
;;; KLUDGE We directly bind ASDF::*OUTPUT-TRANSLATIONS* because ASDF advertised
;;; API does not work.
(defmacro with-asdf-compilation-unit (() &amp;body body)
  `(with-compilation-unit (:target *cc-target*)
     (flet ((cc-path ()
              (merge-pathnames &quot;**/*.*&quot;
                               (uiop:ensure-directory-pathname *cc-cache-dir*))))
       (let ((asdf::*output-translations* `(((t ,(cc-path)))))
             (*load-system-operation* 'load-source-op)
             (*features* (remove-duplicates
                          (list* :asdf :asdf2 :asdf3 :asdf3.1 *features*))))
         ,@body))))
</code></pre>

<p>Note that loading the system should happen in a different environment than
compiling it. Most notably we can't reuse the cache. That's why <code>cross-load-op</code>
must not be a dependency of <code>cross-compile-op</code>. Output translations and features
affect the planning phase, so we need estabilish the environment over <code>operate</code>
and not only <code>perform</code>. We will also define functions for the user to invoke
cross-compilation, to show cross-compilation plan and to wipe the cache:</p>

<pre><code>(defun cross-compile (system &amp;rest args
                      &amp;key cache-dir target load-type &amp;allow-other-keys)
  (let ((*cc-cache-dir* (or cache-dir *cc-cache-dir*))
        (*cc-target* (or target *cc-target*))
        (*cc-load-type* (or load-type *cc-load-type*))
        (cc-operation (make-operation 'cross-compile-op)))
    (apply 'operate cc-operation system args)
    (with-asdf-compilation-unit () ;; ensure cache
      (output-file cc-operation system))))

(defun cross-compile-plan (system target)
  (format *debug-io* &quot;-- Plan for ~s -----------------~%&quot; system)
  (let ((*cc-target* target))
    (with-asdf-compilation-unit ()
      (map nil (lambda (a)
                 (format *debug-io* &quot;~24a: ~a~%&quot; (car a) (cdr a)))
           (asdf::plan-actions
            (make-plan 'sequential-plan 'cross-compile-op system))))))

(defun cross-compile-plan (system target)
  (format *debug-io* &quot;-- Plan for ~s -----------------~%&quot; system)
  (let ((*cc-target* target))
    (with-asdf-compilation-unit ()
      (map nil (lambda (a)
                 (format *debug-io* &quot;~24a: ~a~%&quot; (car a) (cdr a)))
           (asdf::plan-actions
            (make-plan 'sequential-plan 'cross-compile-op system))))))

(defun clear-cc-cache (&amp;key (dir *cc-cache-dir*) (force nil))
  (uiop:delete-directory-tree
   dir
   :validate (or force (yes-or-no-p &quot;Do you want to delete recursively ~S?&quot; dir))
   :if-does-not-exist :ignore))

;;; CROSS-LOAD-OP happens inside the default environment, while the plan for
;;; cross-compilation should have already set the target features.

(defmethod operate ((self cross-compile-op) (c system) &amp;rest args)
  (declare (ignore args))
  (unless (operation-done-p 'cross-load-op c)
    (operate 'cross-load-op c))
  (with-asdf-compilation-unit ()
    (call-next-method)))
</code></pre>

<p>Last but not least we need to specify input and output files for operations.
This will tie into the plan, so that compiled objects will be reused. Computing
input files for cross-compile-op is admittedly hairy, because we need to visit
all dependency systems and collect their outputs too. Dependencies may take
various forms, so we need to normalize them.</p>

<pre><code>(defmethod input-files ((o cross-object-op) (c cl-source-file))
  (list (component-pathname c)))

(defmethod output-files ((o cross-object-op) (c cl-source-file))
  (let ((input-file (component-pathname c)))
    (list (compile-file-pathname input-file :type :object))))

(defmethod input-files ((self cross-compile-op) (c system))
  (let ((visited (make-hash-table :test #'equal))
        (systems nil))
    (labels ((normalize-asdf-system (dep)
               (etypecase dep
                 ((or string symbol)
                  (setf dep (find-system dep)))
                 (system)
                 (cons
                  (ecase (car dep)
                    ;; *features* are bound here to the target.
                    (:feature
                     (destructuring-bind (feature depspec) (cdr dep)
                       (if (member feature *features*)
                           (setf dep (normalize-asdf-system depspec))
                           (setf dep nil))))
                    ;; INV if versions were incompatible, then CROSS-LOAD-OP would bark.
                    (:version
                     (destructuring-bind (depname version) (cdr dep)
                       (declare (ignore version))
                       (setf dep (normalize-asdf-system depname))))
                    ;; Ignore &quot;require&quot;, these are used during system loading.
                    (:require))))
               dep)
             (rec (sys)
               (setf sys (normalize-asdf-system sys))
               (when (null sys)
                 (return-from rec))
               (unless (gethash sys visited)
                 (setf (gethash sys visited) t)
                 (push sys systems)
                 (map nil #'rec (component-sideway-dependencies sys)))))
      (rec c)
      (loop for sys in systems
            append (loop for sub in (asdf::sub-components sys :type 'cl-source-file)
                         collect (output-file 'cross-object-op sub))))))

(defmethod output-files ((self cross-compile-op) (c system))
  (let* ((path (component-pathname c))
         (file (make-pathname :name (primary-system-name c) :defaults path)))
    (list (compile-file-pathname file :type :static-library))))
</code></pre>

<p>At last we can cross compile ASDF systems. Let's give it a try:</p>

<pre><code>ASDF-ECL/CC&gt; (cross-compile-plan &quot;flexi-streams&quot; *wasm-target*)
-- Plan for &quot;flexi-streams&quot; -----------------
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;trivial-gray-streams&quot; &quot;package&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;trivial-gray-streams&quot; &quot;streams&quot;&gt;
#&lt;cross-compile-op &gt;    : #&lt;system &quot;trivial-gray-streams&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;packages&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;mapping&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;ascii&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;koi8-r&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;mac&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;iso-8859&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;enc-cn-tbl&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;code-pages&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;specials&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;util&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;conditions&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;external-format&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;length&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;encode&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;decode&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;in-memory&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;stream&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;output&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;input&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;io&quot;&gt;
#&lt;cross-object-op &gt;     : #&lt;cl-source-file &quot;flexi-streams&quot; &quot;strings&quot;&gt;
#&lt;cross-compile-op &gt;    : #&lt;system &quot;flexi-streams&quot;&gt;
NIL
ASDF-ECL/CC&gt; (cross-compile &quot;flexi-streams&quot; :target *wasm-target*)
;;; ...
#P&quot;/tmp/ecl-cc-cache/libs/flexi-streams-20241012-git/libflexi-streams.a&quot;
</code></pre>

<p>Note that <code>libflexi-streams.a</code> contains all objects from both libraries
<code>flexi-streams</code> and <code>trivial-gray-streams</code>. All artifacts are cached, so if you
remove an object or modify a file, then only necessary parts will be recompiled.</p>

<p>All that is left is to include <code>libflexi-streams.a</code> in <code>make.sh</code> and put the
initialization form in <code>wecl.c</code>:</p>

<pre><code>extern void init_lib_flexi_streams(cl_object);
ecl_init_module(NULL, init_lib_flexi_streams);.
</code></pre>

<p>This should suffice for the first iteration for cross-compiling systems. Next
steps of improvement would be:</p>

<ul>
<li>compiling to static libraries (without dependencies)</li>
<li>compiling to shared libraries (with and without dependencies)</li>
<li>compiling to an executable (final wasm file)</li>
<li>target system emulation (for faithful correspondence between load and compile)</li>
</ul>

<p>The code from this section may be found in <code>wecl</code> repository</p>

<p><a id="org724c21a"></a></p>

<h1>Funding</h1>

<p>This project is funded through <a href="https://nlnet.nl/commonsfund" >NGI0 Commons Fund</a>, a fund established by <a href="https://nlnet.nl" >NLnet</a> with financial support from the European Commission's <a href="https://ngi.eu" >Next Generation Internet</a> program. Learn more at the <a href="https://nlnet.nl/project/ECL" >NLnet project page</a>.</p>

<p><a href="https://nlnet.nl" ><img src="https://nlnet.nl/logo/banner.png" alt="NLnet foundation logo" width="20%" /></a>
<a href="https://nlnet.nl/commonsfund" ><img src="https://nlnet.nl/image/logos/NGI0_tag.svg" alt="NGI Zero Logo" width="20%" /></a></p>
 ]]></description> </item><item> <title>Using Common Lisp from inside the Browser</title> <link>/posts/Using-Common-Lisp-from-inside-the-Browser.html</link> <pubDate>2025-08-21</pubDate> <author>Daniel Kochmański</author> <guid isPermaLink="true">/posts/Using-Common-Lisp-from-inside-the-Browser.html</guid> <category><![CDATA[ lisp ]]></category><category><![CDATA[ webassembly ]]></category> <description><![CDATA[ <h1>Table of Contents</h1>

<ol>
<li><a href="#orgdcf199e" >Scripting a website with Common Lisp</a></li>
<li><a href="#orgd38ed8e" >JS-FFI &#x2013; low level interface</a></li>
<li><a href="#org8b3e676" >LIME/SLUG &#x2013; interacting from Emacs</a></li>
<li><a href="#org490d9cc" >Injecting CL runtime in arbitrary websites</a></li>
<li><a href="#org5264ea7" >Current Caveats</a></li>
<li><a href="#orgfb4da56" >Funding</a></li>
</ol>

<p>Web Embeddable Common Lisp is a project that brings Common Lisp and the Web
Browser environments together. In this post I'll outline the current progress of
the project and provide some technical details, including current caveats and
future plans.</p>

<p>It is important to note that this is not a release and none of the described
APIs and functionalities is considered to be stable. Things are still changing
and I'm not accepting bug reports for the time being.</p>

<p>The source code of the project is available: <a href="https://fossil.turtleware.eu/wecl/">https://fossil.turtleware.eu/wecl/</a>.</p>

<p><a id="orgdcf199e"></a></p>

<h1>Scripting a website with Common Lisp</h1>

<p>The easiest way to use Common Lisp on a website is to include WECL and insert
script tags with a type &quot;text/common-lisp&quot;. When the attribute src is present,
then first the runtime loads the script from that url, and then it executes the
node body. For example create and run this HTML document from localhost:</p>

<pre><code>&lt;!doctype html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Web Embeddable Common Lisp&lt;/title&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;https://turtleware.eu/static/misc/wecl-20250821/easy.css&quot; /&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;https://turtleware.eu/static/misc/wecl-20250821/boot.js&quot;&gt;&lt;/script&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;https://turtleware.eu/static/misc/wecl-20250821/wecl.js&quot;&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;script type=&quot;text/common-lisp&quot; src=&quot;https://turtleware.eu/static/misc/wecl-20250821/easy.lisp&quot; id='easy-script'&gt;
(defvar *div* (make-element &quot;div&quot; :id &quot;my-ticker&quot;))
(append-child [body] *div*)

(dotimes (v 4)
  (push-counter v))

(loop for tic from 6 above 0
      do (replace-children *div* (make-paragraph &quot;~a&quot; tic))
         (js-sleep 1000)
      finally (replace-children *div* (make-paragraph &quot;BOOM!&quot;)))

(show-script-text &quot;easy-script&quot;)
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>We may use Common Lisp that can call to JavaScript, and register callbacks to be
called on specified events. The source code of the script can be found here:</p>

<ul>
<li><a href="https://turtleware.eu/static/misc/wecl-20250821/easy.html">https://turtleware.eu/static/misc/wecl-20250821/easy.html</a></li>
<li><a href="https://turtleware.eu/static/misc/wecl-20250821/easy.lisp">https://turtleware.eu/static/misc/wecl-20250821/easy.lisp</a></li>
</ul>

<p>Because the runtime is included as a script, the browser will usually cache the
~10MB WebAssembly module.</p>

<p><a id="orgd38ed8e"></a></p>

<h1>JS-FFI &#x2013; low level interface</h1>

<p>The initial foreign function interface has numerous macros defining wrappers
that may be used from Common Lisp or passed to JavaScript.</p>

<p>Summary of currently available operators:</p>

<ul>
<li><strong>define-js-variable:</strong> an inlined expression, like <code>document</code></li>
<li><strong>define-js-object:</strong> an object referenced from the object store</li>
<li><strong>define-js-function:</strong> a function</li>
<li><strong>define-js-method:</strong> a method of the argument, like <code>document.foobar()</code></li>
<li><strong>define-js-getter:</strong> a slot reader of the argument</li>
<li><strong>define-js-setter:</strong> a slot writer of the first argument</li>
<li><strong>define-js-accessor:</strong> combines define-js-getter and define-js-setter</li>
<li><strong>define-js-script:</strong> template for JavaScript expressions</li>
<li><strong>define-js-callback:</strong> Common Lisp function reference callable from JavaScript</li>
<li><strong>lambda-js-callback:</strong> anonymous Common Lisp function reference (for closures)</li>
</ul>

<p>Summary of argument types:</p>

<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">


<colgroup>
<col  class="org-left" />

<col  class="org-left" />

<col  class="org-left" />
</colgroup>
<thead>
<tr>
<th scope="col" class="org-left">type name</th>
<th scope="col" class="org-left">lisp side</th>
<th scope="col" class="org-left">js side</th>
</tr>
</thead>
<tbody>
<tr>
<td class="org-left">:object</td>
<td class="org-left">Common Lisp object</td>
<td class="org-left">Common Lisp object reference</td>
</tr>

<tr>
<td class="org-left">:js-ref</td>
<td class="org-left">JavaScript object reference</td>
<td class="org-left">JavaScript object</td>
</tr>

<tr>
<td class="org-left">:fixnum</td>
<td class="org-left">fixnum (coercible)</td>
<td class="org-left">fixnum (coercible)</td>
</tr>

<tr>
<td class="org-left">:symbol</td>
<td class="org-left">symbol</td>
<td class="org-left">symbol (name inlined)</td>
</tr>

<tr>
<td class="org-left">:string</td>
<td class="org-left">string (coercible)</td>
<td class="org-left">string (coercible)</td>
</tr>

<tr>
<td class="org-left">:null</td>
<td class="org-left">nil</td>
<td class="org-left">null</td>
</tr>
</tbody>
</table>

<p>All operators, except for <code>LAMBDA-JS-CALLBACK</code> have a similar lambda list:</p>

<blockquote>
<p>(DEFINE-JS NAME-AND-OPTIONS [ARGUMENTS [,@BODY]])</p>
</blockquote>

<p>The first argument is a list <code>(name &amp;key js-expr type)</code> that is common to all
defining operators:</p>

<ul>
<li><strong>name:</strong> Common Lisp symbol denoting the object</li>
<li><strong>js-expr:</strong> a string denoting the JavaScript expression, i.e &quot;innerText&quot;</li>
<li><strong>type:</strong> a type of the object returned by executing the expression</li>
</ul>

<p>For example:</p>

<pre><code>(define-js-variable ([document] :js-expr &quot;document&quot; :type :symbol))
;; document
(define-js-object ([body] :js-expr &quot;document.body&quot; :type :js-ref))
;; wecl_ensure_object(document.body) /* -&gt; id   */
;; wecl_search_object(id)            /* -&gt; node */
</code></pre>

<p>The difference between a variable and an object in JS-FFI is that variable
expression is executed each time when the object is used (the expression is
inlined), while the object expression is executed only once and the result is
stored in the object store.</p>

<p>The second argument is a list of pairs <code>(name type)</code>. Names will be used in the
lambda list of the operator callable from Common Lisp, while types will be used
to coerce arguments to the type expected by JavaScript.</p>

<pre><code>(define-js-function (parse-float :js-expr &quot;parseFloat&quot; :type :js-ref)
    ((value :string)))
;; parseFloat(value)

(define-js-method (add-event-listener :js-expr &quot;addEventListener&quot; :type :null)
    ((self :js-ref)
     (name :string)
     (fun :js-ref)))
;; self.addEventListener(name, fun)

(define-js-getter (get-inner-text :js-expr &quot;innerText&quot; :type :string)
    ((self :js-ref)))
;; self.innerText

(define-js-setter (set-inner-text :js-expr &quot;innerText&quot; :type :string)
    ((self :js-ref)
     (new :string)))
;; self.innerText = new

(define-js-accessor (inner-text :js-expr &quot;innerText&quot; :type :string)
    ((self :js-ref)
     (new :string)))
;; self.innerText
;; self.innerText = new

(define-js-script (document :js-expr &quot;~a.forEach(~a)&quot; :type :js-ref)
    ((nodes :js-ref)
     (callb :object)))
;; nodes.forEach(callb)
</code></pre>

<p>The third argument is specific to callbacks, where we define Common Lisp body of
the callback. Argument types are used to coerce values from JavaScript to Common
Lisp.</p>

<pre><code>(define-js-callback (print-node :type :object)
    ((elt :js-ref)
     (nth :fixnum)
     (seq :js-ref))
  (format t &quot;Node ~2d: ~a~%&quot; nth elt))

(let ((start 0))
  (add-event-listener *my-elt* &quot;click&quot;
                      (lambda-js-callback :null ((event :js-ref)) ;closure!
                        (incf start)
                        (setf (inner-text *my-elt*)
                              (format nil &quot;Hello World! ~a&quot; start)))
</code></pre>

<p>Note that callbacks are a bit different, because <code>define-js-callback</code> does not
accept <code>js-expr</code> option and <code>lambda-js-callback</code> has unique lambda list. It is
important for callbacks to have an exact arity as they are called with, because
JS-FFI does not implement variable number of arguments yet.</p>

<p>Callbacks can be referred by name with an operator <code>(js-callback name)</code>.</p>

<p><a id="org8b3e676"></a></p>

<h1>LIME/SLUG &#x2013; interacting from Emacs</h1>

<p>While working on FFI I've decided to write an adapter for SLIME/SWANK that will
allow interacting with WECL from Emacs. The principle is simple: we connect with
a websocket to Emacs that is listening on the specified port (i.e on localhost).
This adapter uses the library <code>emacs-websocket</code> written by Andrew Hyatt.</p>

<p>It allows for compiling individual forms with <code>C-c C-c</code>, but file compilation
does not work (because files reside on a different &quot;host&quot;). REPL interaction
works as expected, as well as SLDB. The connection may occasionally be unstable,
and until Common Lisp call returns, the whole page is blocked. Notably waiting
for new requests is not a blocking operation from the JavaScript perspective,
because it is an asynchronous operation.</p>

<p>You may find my changes to SLIME here: <a href="https://github.com/dkochmanski/slime/">https://github.com/dkochmanski/slime/</a>,
and it is proposed upstream here: <a href="https://github.com/slime/slime/pull/879">https://github.com/slime/slime/pull/879</a>.
Before these changes are merged, we'll patch SLIME:</p>

<pre><code>;;; Patches for SLIME 2.31 (to be removed after the patch is merged).
;;; It is assumed that SLIME is already loaded into Emacs.
(defun slime-net-send (sexp proc)
  &quot;Send a SEXP to Lisp over the socket PROC.
This is the lowest level of communication. The sexp will be READ and
EVAL'd by Lisp.&quot;
  (let* ((payload (encode-coding-string
                   (concat (slime-prin1-to-string sexp) &quot;\n&quot;)
                   'utf-8-unix))
         (string (concat (slime-net-encode-length (length payload))
                         payload))
         (websocket (process-get proc :websocket)))
    (slime-log-event sexp)
    (if websocket
        (websocket-send-text websocket string)
      (process-send-string proc string))))

(defun slime-use-sigint-for-interrupt (&amp;optional connection)
  (let ((c (or connection (slime-connection))))
    (cl-ecase (slime-communication-style c)
      ((:fd-handler nil) t)
      ((:spawn :sigio :async) nil))))
</code></pre>

<p>Now we can load the LIME adapter opens a websocket server. The source code may
be downloaded from <a href="https://turtleware.eu/static/misc/wecl-20250821/lime.el">https://turtleware.eu/static/misc/wecl-20250821/lime.el</a>:</p>

<pre><code>;;; lime.el --- Lisp Interaction Mode for Emacs -*-lexical-binding:t-*-
;;; 
;;; This program extends SLIME with an ability to listen for lisp connections.
;;; The flow is reversed - normally SLIME is a client and SWANK is a server.

(require 'websocket)

(defvar *lime-server* nil
  &quot;The LIME server.&quot;)

(cl-defun lime-zipit (obj &amp;optional (start 0) (end 72))
  (let* ((msg (if (stringp obj)
                  obj
                (slime-prin1-to-string obj)))
         (len (length msg)))
    (substring msg (min start len) (min end len))))

(cl-defun lime-message (&amp;rest args)
  (with-current-buffer (process-buffer *lime-server*)
    (goto-char (point-max))
    (dolist (arg args)
      (insert (lime-zipit arg)))
    (insert &quot;\n&quot;)
    (goto-char (point-max))))

(cl-defun lime-client-process (client)
  (websocket-conn client))

(cl-defun lime-process-client (process)
  (process-get process :websocket))

;;; c.f slime-net-connect
(cl-defun lime-add-client (client)
  (lime-message &quot;LIME connecting a new client&quot;)
  (let* ((process (websocket-conn client))
         (buffer (generate-new-buffer &quot;*lime-connection*&quot;)))
    (set-process-buffer process buffer)
    (push process slime-net-processes)
    (slime-setup-connection process)
    client))

;;; When SLIME kills the process, then it invokes LIME-DISCONNECT hook.
;;; When SWANK kills the process, then it invokes LIME-DEL-CLIENT hook.
(cl-defun lime-del-client (client)
  (when-let ((process (lime-client-process client)))
    (lime-message &quot;LIME client disconnected&quot;)
    (slime-net-sentinel process &quot;closed by peer&quot;)))

(cl-defun lime-disconnect (process)
  (when-let ((client (lime-process-client process)))
    (lime-message &quot;LIME disconnecting client&quot;)
    (websocket-close client)))

(cl-defun lime-on-error (client fun error)
  (ignore client fun)
  (lime-message &quot;LIME error: &quot; (slime-prin1-to-string error)))

;;; Client sends the result over a websocket. Handling responses is implemented
;;; by SLIME-NET-FILTER. As we can see, the flow is reversed in our case.
(cl-defun lime-handle-message (client frame)
  (let ((process (lime-client-process client))
        (data (websocket-frame-text frame)))
    (lime-message &quot;LIME-RECV: &quot; data)
    (slime-net-filter process data)))

(cl-defun lime-net-listen (host port &amp;rest parameters)
  (when *lime-server*
    (error &quot;LIME server has already started&quot;))
  (setq *lime-server*
        (apply 'websocket-server port
               :host host
               :on-open    (function lime-add-client)
               :on-close   (function lime-del-client)
               :on-error   (function lime-on-error)
               :on-message (function lime-handle-message)
               parameters))
  (unless (memq 'lime-disconnect slime-net-process-close-hooks)
    (push 'lime-disconnect slime-net-process-close-hooks))
  (let ((buf (get-buffer-create &quot;*lime-server*&quot;)))
    (set-process-buffer *lime-server* buf)
    (lime-message &quot;Welcome &quot; *lime-server* &quot;!&quot;)
    t))

(cl-defun lime-stop ()
  (when *lime-server*
   (websocket-server-close *lime-server*)
   (setq *lime-server* nil)))
</code></pre>

<p>After loading this file into Emacs invoke <code>(lime-net-listen &quot;localhost&quot; 8889)</code>.
Now our Emacs listens for new connections from SLUG (the lisp-side part adapting
SWANK, already bundled with WECL). There are two SLUG backends in a repository:</p>

<ul>
<li><strong>WANK:</strong> for web browser environment</li>
<li><strong>FRIG:</strong> for Common Lisp runtime (uses <code>websocket-driver-client</code>)</li>
</ul>

<p>Now you can open a page listed here and connect to SLIME:</p>

<pre><code>&lt;!doctype html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Web Embeddable Common Lisp&lt;/title&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;easy.css&quot; /&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;https://turtleware.eu/static/misc/wecl-20250821/boot.js&quot;&gt;&lt;/script&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;https://turtleware.eu/static/misc/wecl-20250821/wecl.js&quot;&gt;&lt;/script&gt;
    &lt;script type=&quot;text/common-lisp&quot; src=&quot;https://turtleware.eu/static/misc/wecl-20250821/slug.lisp&quot;&gt;&lt;/script&gt;
    &lt;script type=&quot;text/common-lisp&quot; src=&quot;https://turtleware.eu/static/misc/wecl-20250821/wank.lisp&quot;&gt;&lt;/script&gt;
    &lt;script type=&quot;text/common-lisp&quot; src=&quot;https://turtleware.eu/static/misc/wecl-20250821/easy.lisp&quot;&gt;
      (defvar *connect-button* (make-element &quot;button&quot; :text &quot;Connect&quot;))
      (define-js-callback (connect-to-slug :type :null) ((event :js-ref))
        (wank-connect &quot;localhost&quot; 8889)
        (setf (inner-text *connect-button*) &quot;Crash!&quot;))
      (add-event-listener *connect-button* &quot;click&quot; (js-callback connect-to-slug))
      (append-child [body] *connect-button*)
    &lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>

<ul>
<li><a href="https://turtleware.eu/static/misc/wecl-20250821/slug.html">https://turtleware.eu/static/misc/wecl-20250821/slug.html</a>.</li>
</ul>

<p>This example shows an important limitation &#x2013; <span class="underline">Emscripten</span> does not allow for
multiple asynchronous contexts in the same thread. That means that if Lisp call
doesn't return (i.e because it waits for input in a loop), then we can't execute
other Common Lisp statements from elsewhere because the application will crash.</p>

<p><a id="org490d9cc"></a></p>

<h1>Injecting CL runtime in arbitrary websites</h1>

<p>Here's another example. It is more a cool gimmick than anything else, but let's
try it. Open a console on this very website (on firefox C-S-i) and execute:</p>

<pre><code>function inject_js(url) {
    var head = document.getElementsByTagName('head')[0];
    var script = document.createElement('script');
    head.appendChild(script);
    script.type = 'text/javascript';
    return new Promise((resolve) =&gt; {
        script.onload = resolve;
        script.src = url;
    });
}

function inject_cl() {
    wecl_eval('(wecl/impl::js-load-slug &quot;https://turtleware.eu/static/misc/wecl-20250821&quot;)');
}

inject_js('https://turtleware.eu/static/misc/wecl-20250821/boot.js')
    .then(() =&gt; {
        wecl_init_hooks.push(inject_cl);
        inject_js('https://turtleware.eu/static/misc/wecl-20250821/wecl.js');
    });
</code></pre>

<p>With this, assuming that you've kept your LIME server open, you'll have a REPL
onto uncooperative website. Now we can fool around with queries and changes:</p>

<pre><code>(define-js-accessor (title :js-expr &quot;title&quot; :type :string)
  ((self :js-ref)
   (title :string)))

(define-js-accessor (background :js-expr &quot;body.style.backgroundColor&quot; :type :string)
  ((self :js-ref)
   (background :string)))

(setf (title [document]) &quot;Write in Lisp!&quot;)
(setf (background [document]) &quot;#aaffaa&quot;)
</code></pre>

<p><a id="org5264ea7"></a></p>

<h1>Current Caveats</h1>

<p>The first thing to address is the lack of threading primitives. Native threads
can be implemented with web workers, but then our GC wouldn't know how to stop
the world to clean up. Another option is to use cooperative threads, but that
also won't work, because Emscripten doesn't support independent asynchronous
contexts, nor ECL is ready for that yet.</p>

<p>I plan to address both issues simultaneously in the second stage of the project
when I port the runtime to WASI. We'll be able to use browser's GC, so running
in multiple web workers should not be a problem anymore. Unwinding and rewinding
the stack will require tinkering with ASYNCIFY and I have somewhat working green
threads implementation in place, so I will finish it and upstream in ECL.</p>

<p>Currently I'm focusing mostly on having things working, so JS and CL interop is
brittle and often relies on evaluating expressions, trampolining and coercing.
That impacts the performance in a significant way. Moreover all loaded scripts
are compiled with a one-pass compiler, so the result bytecode is not optimized.</p>

<p>There is no support for loading cross-compiled files onto the runtime, not to
mention that it is not possible to precompile systems with ASDF definitions.</p>

<p>JS-FFI requires more work to allow for defining functions with variable number
of arguments and with optional arguments. There is no dynamic coercion of
JavaScript exceptions to Common Lisp conditions, but it is planned.</p>

<p><a id="orgfb4da56"></a></p>

<h1>Funding</h1>

<p>This project is funded through <a href="https://nlnet.nl/commonsfund" >NGI0 Commons Fund</a>, a fund established by <a href="https://nlnet.nl" >NLnet</a> with financial support from the European Commission's <a href="https://ngi.eu" >Next Generation Internet</a> program. Learn more at the <a href="https://nlnet.nl/project/ECL" >NLnet project page</a>.</p>

<p><a href="https://nlnet.nl" ><img src="https://nlnet.nl/logo/banner.png" alt="NLnet foundation logo" width="20%" /></a>
<a href="https://nlnet.nl/commonsfund" ><img src="https://nlnet.nl/image/logos/NGI0_tag.svg" alt="NGI Zero Logo" width="20%" /></a></p>
 ]]></description> </item><item> <title>Dynamic Vars - Return of the Jedi</title> <link>/posts/Dynamic-Vars---Return-of-the-Jedi.html</link> <pubDate>2024-11-04</pubDate> <author>Daniel Kochmański</author> <guid isPermaLink="true">/posts/Dynamic-Vars---Return-of-the-Jedi.html</guid> <category><![CDATA[ lisp ]]></category><category><![CDATA[ mop ]]></category><category><![CDATA[ tutorial ]]></category> <description><![CDATA[ <h1>Table of Contents</h1>

<ol>
<li><a href="#orgb91d016" >The protocol</a></li>
<li><a href="#org3ae3a91" >Control operators</a></li>
<li><a href="#org0b2c083" >Synchronized hash tables with weakness</a></li>
<li><a href="#orgc3768a9" >First-class dynamic variables</a>

<ol>
<li><a href="#orgd64b71d" ><code>STANDARD-DYNAMIC-VARIABLE</code></a></li>
<li><a href="#orgf390be8" ><code>SURROGATE-DYNAMIC-VARIABLE</code></a></li>
</ol></li>
<li><a href="#orga0cee01" >Thread-local variables</a>

<ol>
<li><a href="#orgf7cd1ce" >The protocol</a></li>
<li><a href="#org4046586" >The implementation</a></li>
</ol></li>
<li><a href="#org0da68e3" >Thread-local slots</a></li>
<li><a href="#orgd6a801a" >What can we use it for?</a></li>
</ol>

<p>In the previous two posts I've presented an implementation of first-class
dynamic variables using <code>PROGV</code> and a surrogate implementation for SBCL.</p>

<p>Now we will double down on this idea and make the protocol extensible. Finally
we'll implement a specialized version of dynamic variables where even the top
level value of the variable is thread-local.</p>

<p><a id="orgb91d016"></a></p>

<h1>The protocol</h1>

<p>Previously we've defined operators as either macros or functions. Different
implementations were protected by the feature flag and symbols collided. Now we
will introduce the protocol composed of a common superclass and functions that
are specialized by particular implementations.</p>

<p>Most notably we will introduce a new operator <code>CALL-WITH-DYNAMIC-VARIABLE</code> that
is responsible for establishing a single binding. Thanks to that it will be
possible to mix dynamic variables of different types within a single <code>DLET</code>
statement.</p>

<pre><code>(defclass dynamic-variable () ())

(defgeneric dynamic-variable-bindings (dvar))
(defgeneric dynamic-variable-value (dvar))
(defgeneric (setf dynamic-variable-value) (value dvar))
(defgeneric dynamic-variable-bound-p (dvar))
(defgeneric dynamic-variable-makunbound (dvar))
(defgeneric call-with-dynamic-variable (cont dvar &amp;optional value))
</code></pre>

<p>Moreover we'll define a constructor that is specializable by a key. This design
will allow us to refer to the dynamic variable class by using a shorter name.
We will also define the standard class to be used and an matching constructor.</p>

<pre><code>(defparameter *default-dynamic-variable-class*
  #-fake-progv-kludge 'standard-dynamic-variable
  #+fake-progv-kludge 'surrogate-dynamic-variable)

(defgeneric make-dynamic-variable-using-key (key &amp;rest initargs)
  (:method (class &amp;rest initargs)
    (apply #'make-instance class initargs))
  (:method ((class (eql t)) &amp;rest initargs)
    (apply #'make-instance *default-dynamic-variable-class* initargs))
  (:method ((class null) &amp;rest initargs)
    (declare (ignore class initargs))
    (error &quot;Making a dynamic variable that is not, huh?&quot;)))

(defun make-dynamic-variable (&amp;rest initargs)
  (apply #'make-dynamic-variable-using-key t initargs))
</code></pre>

<p><a id="org3ae3a91"></a></p>

<h1>Control operators</h1>

<p>Control operators are the same as previously, that is a set of four macros that
consume the protocol specified above. Note that <code>DYNAMIC-VARIABLE-PROGV</code> expands
to a recursive call where each binding is processed separately.</p>

<pre><code>(defmacro dlet (bindings &amp;body body)
  (flet ((pred (binding)
           (and (listp binding) (= 2 (length binding)))))
    (unless (every #'pred bindings)
      (error &quot;DLET: bindings must be lists of two values.~%~
              Invalid bindings:~%~{ ~s~%~}&quot; (remove-if #'pred bindings))))
  (loop for (var val) in bindings
        collect var into vars
        collect val into vals
        finally (return `(dynamic-variable-progv (list ,@vars) (list ,@vals)
                           ,@body))))

(defmacro dset (&amp;rest pairs)
  `(setf ,@(loop for (var val) on pairs by #'cddr
                 collect `(dref ,var)
                 collect val)))

(defmacro dref (variable)
  `(dynamic-variable-value ,variable))

(defun call-with-dynamic-variable-progv (cont vars vals)
  (flet ((thunk ()
           (if vals
               (call-with-dynamic-variable cont (car vars) (car vals))
               (call-with-dynamic-variable cont (car vars)))))
    (if vars
        (call-with-dynamic-variable-progv #'thunk (cdr vars) (cdr vals))
        (funcall cont))))

(defmacro dynamic-variable-progv (vars vals &amp;body body)
  (let ((cont (gensym)))
    `(flet ((,cont () ,@body))
       (call-with-dynamic-variable-progv (function ,cont) ,vars ,vals))))
</code></pre>

<p><a id="org0b2c083"></a></p>

<h1>Synchronized hash tables with weakness</h1>

<p>Previously we've used SBCL-specific options to define a synchronized hash table
with weak keys. This won't do anymore, because we will need a similar object to
implement the thread-local storage for top level values.</p>

<p><code>trivial-garbage</code> is a portability layer that allows to define hash tables with
a specified weakness, but it does not provide an argument that would abstract
away synchronization. We will ensure thread-safety with locks instead.</p>

<pre><code>(defclass tls-table ()
  ((table :initform (trivial-garbage:make-weak-hash-table
                     :test #'eq :weakness :key))
   (lock :initform (bt:make-lock))))

(defun make-tls-table ()
  (make-instance 'tls-table))

(defmacro with-tls-table ((var self) &amp;body body)
  (let ((obj (gensym)))
    `(let* ((,obj ,self)
            (,var (slot-value ,obj 'table)))
       (bt:with-lock-held ((slot-value ,obj 'lock)) ,@body))))
</code></pre>

<p><a id="orgc3768a9"></a></p>

<h1>First-class dynamic variables</h1>

<p><a id="orgd64b71d"></a></p>

<h2><code>STANDARD-DYNAMIC-VARIABLE</code></h2>

<p>Previously in the default implementation we've represented dynamic variables
with a symbol. The new implementation is similar except that the symbol is read
from a <code>STANDARD-OBJECT</code> that represents the variable. This also enables us to
specialize the function <code>CALL-WITH-DYNAMIC-VARIABLE</code>:</p>

<pre><code>(defclass standard-dynamic-variable (dynamic-variable)
  ((symbol :initform (gensym) :accessor dynamic-variable-bindings)))

(defmethod dynamic-variable-value ((dvar standard-dynamic-variable))
  (symbol-value (dynamic-variable-bindings dvar)))

(defmethod (setf dynamic-variable-value) (value (dvar standard-dynamic-variable))
  (setf (symbol-value (dynamic-variable-bindings dvar)) value))

(defmethod dynamic-variable-bound-p ((dvar standard-dynamic-variable))
  (boundp (dynamic-variable-bindings dvar)))

(defmethod dynamic-variable-makunbound ((dvar standard-dynamic-variable))
  (makunbound (dynamic-variable-bindings dvar)))

(defmethod call-with-dynamic-variable (cont (dvar standard-dynamic-variable)
                                       &amp;optional (val nil val-p))
  (progv (list (dynamic-variable-bindings dvar)) (if val-p (list val) ())
    (funcall cont)))
</code></pre>

<p><a id="orgf390be8"></a></p>

<h2><code>SURROGATE-DYNAMIC-VARIABLE</code></h2>

<p>The implementation of the <code>SURROGATE-DYNAMIC-VARIABLE</code> is almost the same as
previously. The only difference is that we use the previously defined
indirection to safely work with hash tables. Also note, that we are not add the
feature condition - both classes is always created.</p>

<pre><code>(defvar +fake-unbound+ 'unbound)
(defvar +cell-unbound+ '(no-binding))

(defclass surrogate-dynamic-variable (dynamic-variable)
  ((tls-table
    :initform (make-tls-table)
    :reader dynamic-variable-tls-table)
   (top-value
    :initform +fake-unbound+
    :accessor dynamic-variable-top-value)))

(defmethod dynamic-variable-bindings ((dvar surrogate-dynamic-variable))
  (let ((process (bt:current-thread)))
    (with-tls-table (tls-table (dynamic-variable-tls-table dvar))
      (gethash process tls-table +cell-unbound+))))

(defmethod (setf dynamic-variable-bindings) (value (dvar surrogate-dynamic-variable))
  (let ((process (bt:current-thread)))
    (with-tls-table (tls-table (dynamic-variable-tls-table dvar))
      (setf (gethash process tls-table) value))))

(defun %dynamic-variable-value (dvar)
  (let ((tls-binds (dynamic-variable-bindings dvar)))
    (if (eq tls-binds +cell-unbound+)
        (dynamic-variable-top-value dvar)
        (car tls-binds))))

(defmethod dynamic-variable-value ((dvar surrogate-dynamic-variable))
  (let ((tls-value (%dynamic-variable-value dvar)))
    (when (eq tls-value +fake-unbound+)
      (error 'unbound-variable :name &quot;(unnamed)&quot;))
    tls-value))

(defmethod (setf dynamic-variable-value) (value (dvar surrogate-dynamic-variable))
  (let ((tls-binds (dynamic-variable-bindings dvar)))
    (if (eq tls-binds +cell-unbound+)
        (setf (dynamic-variable-top-value dvar) value)
        (setf (car tls-binds) value))))

(defmethod dynamic-variable-bound-p ((dvar surrogate-dynamic-variable))
  (not (eq +fake-unbound+ (%dynamic-variable-value dvar))))

(defmethod dynamic-variable-makunbound ((dvar surrogate-dynamic-variable))
  (setf (dynamic-variable-value dvar) +fake-unbound+))


;;; Apparently CCL likes to drop^Helide some writes and that corrupts bindings
;;; table. Let's ensure that the value is volatile.
#+ccl (defvar *ccl-ensure-volatile* nil)
(defmethod call-with-dynamic-variable (cont (dvar surrogate-dynamic-variable)
                                       &amp;optional (val +fake-unbound+))
  (push val (dynamic-variable-bindings dvar))
  (let (#+ccl (*ccl-ensure-volatile* (dynamic-variable-bindings dvar)))
    (unwind-protect (funcall cont)
      (pop (dynamic-variable-bindings dvar)))))
</code></pre>

<p><a id="orga0cee01"></a></p>

<h1>Thread-local variables</h1>

<p>We've refactored the previous code to be extensible. Now we can use metaobjects
from the previous post without change. We can also test both implementations in
the same process interchangeably by customizing the default class parameter.</p>

<p>It is the time now to have some fun and extend dynamic variables into variables
with top value not shared between different threads. This will enable ultimate
thread safety. With our new protocol the implementation is trivial!</p>

<p><a id="orgf7cd1ce"></a></p>

<h2>The protocol</h2>

<p>First we will define the protocol class. <code>THREAD-LOCAL-VARIABLE</code> is a variant of
a <code>DYNAMIC-VARIABLE</code> with thread-local top values.</p>

<p>We specify initialization arguments <code>:INITVAL</code> and <code>:INITFUN</code> that will be used
to assign the top value of a binding. The difference is that <code>INITVAL</code> specifies
a single value, while <code>INITFUN</code> can produce an unique object on each invocation.
<code>INITARG</code> takes a precedence over <code>INTIFUN</code>, and if neither is supplied, then a
variable is unbound.</p>

<p>We include the constructor that builds on <code>MAKE-DYNAMIC-VARIABLE-USING-KEY</code>, and
macros corresponding to <code>DEFVAR</code> and <code>DEFPARAMETER</code>. Note that they expand to
<code>:INITFUN</code> - this assures that the initialization form is re-evaluated for each
new thread where the variable is used.</p>

<pre><code>(defclass thread-local-variable (dynamic-variable) ())

(defmethod initialize-instance :after
    ((self thread-local-variable) &amp;key initfun initval)
  (declare (ignore self initfun initval)))

(defparameter *default-thread-local-variable-class*
  #-fake-progv-kludge 'standard-thread-local-variable
  #+fake-progv-kludge 'surrogate-thread-local-variable)

(defun make-thread-local-variable (&amp;rest initargs)
  (apply #'make-dynamic-variable-using-key
         *default-thread-local-variable-class* initargs))

(defmacro create-tls-variable (&amp;optional (form nil fp) &amp;rest initargs)
  `(make-thread-local-variable 
    ,@(when fp `(:initfun (lambda () ,form)))
    ,@initargs))

(defmacro define-tls-variable (name &amp;rest initform-and-initargs)
  `(defvar ,name (create-tls-variable ,@initform-and-initargs)))

(defmacro define-tls-parameter (name &amp;rest initform-and-initargs)
  `(defparameter ,name (create-tls-variable ,@initform-and-initargs)))
</code></pre>

<p>Perhaps it is a good time to introduce a new convention for tls variable names.
I think that surrounding names with the minus sign is a nice idea, because it
signifies, that it is something less than a global value. For example:</p>

<pre><code>DYNAMIC-VARS&gt; (define-tls-variable -context- 
                  (progn
                    (print &quot;Initializing context!&quot;)
                    (list :context)))
-CONTEXT-
DYNAMIC-VARS&gt; -context-
#&lt;a EU.TURTLEWARE.DYNAMIC-VARS::STANDARD-THREAD-LOCAL-VARIABLE 0x7f7636c08640&gt;
DYNAMIC-VARS&gt; (dref -context-)

&quot;Initializing context!&quot; 
(:CONTEXT)
DYNAMIC-VARS&gt; (dref -context-)
(:CONTEXT)
DYNAMIC-VARS&gt; (dset -context- :the-new-value)

:THE-NEW-VALUE
DYNAMIC-VARS&gt; (dref -context-)
:THE-NEW-VALUE
DYNAMIC-VARS&gt; (bt:make-thread
               (lambda ()
                 (print &quot;Let's read it!&quot;)
                 (print (dref -context-))))
#&lt;process &quot;Anonymous thread&quot; 0x7f7637a26cc0&gt;

&quot;Let's read it!&quot; 
&quot;Initializing context!&quot; 
(:CONTEXT) 
DYNAMIC-VARS&gt; (dref -context-)
:THE-NEW-VALUE
</code></pre>

<p><a id="org4046586"></a></p>

<h2>The implementation</h2>

<p>You might have noticed the inconspicuous operator <code>DYNAMIC-VARIABLE-BINDINGS</code>
that is part of the protocol. It returns an opaque object that represents values
of the dynamic variable in the current context:</p>

<ul>
<li>for <code>STANDARD-DYNAMIC-VARIABLE</code>  it is a symbol</li>
<li>for <code>SURROGATE-DYNAMIC-VARIABLE</code> it is a thread-local list of bindings</li>
</ul>

<p>In any case all other operators first take this object and then use it to read,
write or bind the value. The gist of the tls variables implementation is to
always return an object that is local to the thread. To store these objects we
will use the <code>tls-table</code> we've defined earlier.</p>

<pre><code>(defclass thread-local-variable-mixin (dynamic-variable)
  ((tls-table
    :initform (make-tls-table)
    :reader dynamic-variable-tls-table)
   (tls-initfun
    :initarg :initfun
    :initform nil
    :accessor thread-local-variable-initfun)
   (tls-initval
    :initarg :initval
    :initform +fake-unbound+
    :accessor thread-local-variable-initval)))
</code></pre>

<p>For the class <code>STANDARD-THREAD-LOCAL-VARIABLE</code> we will simply return a
different symbol depending on the thread:</p>

<pre><code>(defclass standard-thread-local-variable (thread-local-variable-mixin
                                         thread-local-variable
                                         standard-dynamic-variable)
  ())

(defmethod dynamic-variable-bindings ((tvar standard-thread-local-variable))
  (flet ((make-new-tls-bindings ()
           (let ((symbol (gensym))
                 (initval (thread-local-variable-initval tvar))
                 (initfun (thread-local-variable-initfun tvar)))
             (cond
               ((not (eq +fake-unbound+ initval))
                (setf (symbol-value symbol) initval))
               ((not (null initfun))
                (setf (symbol-value symbol) (funcall initfun))))
             symbol)))
    (let ((key (bt:current-thread)))
      (with-tls-table (tls-table (dynamic-variable-tls-table tvar))
        (or (gethash key tls-table)
            (setf (gethash key tls-table)
                  (make-new-tls-bindings)))))))
</code></pre>

<p>And for the class <code>SURROGATE-THREAD-LOCAL-VARIABLE</code> the only difference from the
<code>SURROGATE-DYNAMIC-VARIABLE</code> implementation is to cons a new list as the initial
value (even when it is unbound) to ensure it is not <code>EQ</code> to <code>+CELL-UNBOUND+</code>.</p>

<pre><code>(defclass surrogate-thread-local-variable (thread-local-variable-mixin
                                          thread-local-variable
                                          surrogate-dynamic-variable)
  ())

(defmethod dynamic-variable-bindings ((tvar surrogate-thread-local-variable))
  (flet ((make-new-tls-bindings ()
           (let ((initval (thread-local-variable-initval tvar))
                 (initfun (thread-local-variable-initfun tvar)))
             (cond
               ((not (eq +fake-unbound+ initval))
                (list initval))
               ((not (null initfun))
                (list (funcall initfun)))
               (t
                (list +fake-unbound+))))))
    (let ((key (bt:current-thread)))
      (with-tls-table (tls-table (dynamic-variable-tls-table tvar))
        (or (gethash key tls-table)
            (setf (gethash key tls-table)
                  (make-new-tls-bindings)))))))
</code></pre>

<p>That's all, now we have two implementations of thread-local variables.
Ramifications are similar as with &quot;ordinary&quot; dynamic variables - the standard
implementation is not advised for SBCL, because it will crash in <code>LDB</code>.</p>

<p><a id="org0da68e3"></a></p>

<h1>Thread-local slots</h1>

<p>First we are going to allow to defined dynamic variable types with an
abbreviated names. This will enable us to specify in the slot definition that
type, for example <code>(MY-SLOT :DYNAMIC :TLS :INITFORM 34)</code></p>

<pre><code>;;; Examples how to add shorthand type names for the dynamic slots:

(defmethod make-dynamic-variable-using-key ((key (eql :tls)) &amp;rest initargs)
  (apply #'make-dynamic-variable-using-key
         *default-thread-local-variable-class* initargs))

(defmethod make-dynamic-variable-using-key ((key (eql :normal-tls)) &amp;rest initargs)
  (apply #'make-dynamic-variable-using-key
         'standard-thread-local-variable initargs))

(defmethod make-dynamic-variable-using-key ((key (eql :kludge-tls)) &amp;rest initargs)
  (apply #'make-dynamic-variable-using-key
         'surrogate-thread-local-variable initargs))

;;; For *DEFAULT-DYNAMIC-VARIABLE* specify :DYNAMIC T.

(defmethod make-dynamic-variable-using-key ((key (eql :normal-dyn)) &amp;rest initargs)
  (apply #'make-dynamic-variable-using-key
         'standard-dynamic-variable initargs))

(defmethod make-dynamic-variable-using-key ((key (eql :kludge-dyn)) &amp;rest initargs)
  (apply #'make-dynamic-variable-using-key
         'surrogate-dynamic-variable initargs))
</code></pre>

<p>In order to do that, we need to remember he value of the argument <code>:DYNAMIC</code>. We
will read it with <code>DYNAMIC-VARIABLE-TYPE</code> and that value will be available in
both direct and the effective slot:</p>

<pre><code>;;; Slot definitions
;;; There is a considerable boilerplate involving customizing slots.
;;;
;;; - direct slot definition: local to a single defclass form
;;;
;;; - effective slot definition: combination of all direct slots with the same
;;;   name in the class and its superclasses
;;;
(defclass dynamic-direct-slot (mop:standard-direct-slot-definition)
  ((dynamic :initform nil :initarg :dynamic :reader dynamic-variable-type)))

;;; The metaobject protocol did not specify an elegant way to communicate
;;; between the direct slot definition and the effective slot definition.
;;; Luckily we have dynamic bindings! :-)
(defvar *kludge/mop-deficiency/dynamic-variable-type* nil)

;;; DYNAMIC-EFFECTIVE-SLOT is implemented to return as slot-value values of the
;;; dynamic variable that is stored with the instance.
;;;
;;; It would be nice if we could specify :ALLOCATION :DYNAMIC for the slot, but
;;; then STANDARD-INSTANCE-ACCESS would go belly up. We could make a clever
;;; workaround, but who cares?
(defclass dynamic-effective-slot (mop:standard-effective-slot-definition)
  ((dynamic :initform *kludge/mop-deficiency/dynamic-variable-type*
            :reader dynamic-variable-type)))
</code></pre>

<p>Moreover we specialize the function <code>MAKE-DYNAMIC-VARIABLE-USING-KEY</code> to the
effective slot class. The initargs in this method are meant for the instance.
When the dynamic variable is created, we check whether it is a thread-local
variable and initialize its <code>INITVAL</code> and <code>INITFUN</code> to values derived from
<code>INITARGS</code>, <code>MOP:SLOT-DEFINITION-INITARGS</code> and <code>MOP:SLOT-DEFINITION-INITFUN</code>:</p>

<pre><code>(defmethod make-dynamic-variable-using-key
    ((key dynamic-effective-slot) &amp;rest initargs)
  (let* ((dvar-type (dynamic-variable-type key))
         (dvar (make-dynamic-variable-using-key dvar-type)))
    (when (typep dvar 'thread-local-variable)
      (loop with slot-initargs = (mop:slot-definition-initargs key)
            for (key val) on initargs by #'cddr
            when (member key slot-initargs) do
              (setf (thread-local-variable-initval dvar) val))
      (setf (thread-local-variable-initfun dvar)
            (mop:slot-definition-initfunction key)))
    dvar))
</code></pre>

<p>The rest of the implementation of <code>DYNAMIC-EFFECTIVE-SLOT</code> is unchanged:</p>

<pre><code>(defmethod mop:slot-value-using-class
    ((class standard-class)
     object
     (slotd dynamic-effective-slot))
  (dref (slot-dvar object slotd)))

(defmethod (setf mop:slot-value-using-class)
    (new-value
     (class standard-class)
     object
     (slotd dynamic-effective-slot))
  (dset (slot-dvar object slotd) new-value))

(defmethod mop:slot-boundp-using-class
  ((class standard-class)
   object
   (slotd dynamic-effective-slot))
  (dynamic-variable-bound-p (slot-dvar object slotd)))

(defmethod mop:slot-makunbound-using-class
  ((class standard-class)
   object
   (slotd dynamic-effective-slot))
  (dynamic-variable-makunbound (slot-dvar object slotd)))
</code></pre>

<p>The implementation of <code>CLASS-WITH-DYNAMIC-SLOTS</code> is also very similar. The first
difference in that <code>ALLOCATE-INSTANCE</code> calls <code>MAKE-DYNAMIC-VARIABLE-USING-KEY</code>
instead of <code>MAKE-DYNAMIC-VARIABLE</code> and supplies the effective slot definition as
the key, and the instance initargs as the remaining arguments. Note that at this
point initargs are already validated by <code>MAKE-INSTANCE</code>. The second difference
is that <code>MOP:COMPUTE-EFFECTIVE-SLOT-DEFINITION</code> binds the flag
<code>*KLUDGE/MOP-DEFICIENCY/DYNAMIC-VARIABLE-TYPE*</code> to <code>DYNAMIC-VARIABLE-TYPE</code>.</p>

<pre><code>;;; This is a metaclass that allows defining dynamic slots that are bound with
;;; the operator SLOT-DLET, and, depending on the type, may have thread-local
;;; top value.
;;;
;;; The metaclass CLASS-WITH-DYNAMIC-SLOTS specifies alternative effective slot
;;; definitions for slots with an initarg :dynamic.
(defclass class-with-dynamic-slots (standard-class) ())

;;; Class with dynamic slots may be subclasses of the standard class.
(defmethod mop:validate-superclass ((class class-with-dynamic-slots)
                                    (super standard-class))
  t)

;;; When allocating the instance we initialize all slots to a fresh symbol that
;;; represents the dynamic variable.
(defmethod allocate-instance ((class class-with-dynamic-slots) &amp;rest initargs)
  (let ((object (call-next-method)))
    (loop for slotd in (mop:class-slots class)
          when (typep slotd 'dynamic-effective-slot) do
            (setf (mop:standard-instance-access
                   object
                   (mop:slot-definition-location slotd))
                  (apply #'make-dynamic-variable-using-key slotd initargs)))
    object))

;;; To improve potential composability of CLASS-WITH-DYNAMIC-SLOTS with other
;;; metaclasses we treat specially only slots that has :DYNAMIC in initargs,
;;; otherwise we call the next method.
(defmethod mop:direct-slot-definition-class
    ((class class-with-dynamic-slots) &amp;rest initargs)
  (loop for (key) on initargs by #'cddr
        when (eq key :dynamic)
          do (return-from mop:direct-slot-definition-class
               (find-class 'dynamic-direct-slot)))
  (call-next-method))

(defmethod mop:compute-effective-slot-definition
    ((class class-with-dynamic-slots)
     name
     direct-slotds)
  (declare (ignore name))
  (let ((latest-slotd (first direct-slotds)))
    (if (typep latest-slotd 'dynamic-direct-slot)
        (let ((*kludge/mop-deficiency/dynamic-variable-type*
                (dynamic-variable-type latest-slotd)))
          (call-next-method))
        (call-next-method))))

(defmethod mop:effective-slot-definition-class
    ((class class-with-dynamic-slots) &amp;rest initargs)
  (declare (ignore initargs))
  (if *kludge/mop-deficiency/dynamic-variable-type*
      (find-class 'dynamic-effective-slot)
      (call-next-method)))
</code></pre>

<p>Finally the implementation of <code>SLOT-DLET</code> does not change:</p>

<pre><code>;;; Accessing and binding symbols behind the slot. We don't use SLOT-VALUE,
;;; because it will return the _value_ of the dynamic variable, and not the
;;; variable itself.
(defun slot-dvar (object slotd)
  (check-type slotd dynamic-effective-slot)
  (mop:standard-instance-access
   object (mop:slot-definition-location slotd)))

(defun slot-dvar* (object slot-name)
  (let* ((class (class-of object))
         (slotd (find slot-name (mop:class-slots class)
                      :key #'mop:slot-definition-name)))
    (slot-dvar object slotd)))

(defmacro slot-dlet (bindings &amp;body body)
  `(dlet ,(loop for ((object slot-name) val) in bindings
                collect `((slot-dvar* ,object ,slot-name) ,val))
     ,@body))
</code></pre>

<p>Finally we can define a class with slots that do not share the top value:</p>

<pre><code>DYNAMIC-VARS&gt; (defclass c1 ()
                  ((slot1 :initarg :slot1 :dynamic nil :accessor slot1)
                   (slot2 :initarg :slot2 :dynamic t   :accessor slot2)
                   (slot3 :initarg :slot3 :dynamic :tls :accessor slot3))
                  (:metaclass class-with-dynamic-slots))
#&lt;The EU.TURTLEWARE.DYNAMIC-VARS::CLASS-WITH-DYNAMIC-SLOTS EU.TURTLEWARE.DYNAMIC-VARS::C1&gt;
DYNAMIC-VARS&gt; (with-slots (slot1 slot2 slot3) *object*
                (setf slot1 :x slot2 :y slot3 :z)
                (list slot1 slot2 slot3))
(:X :Y :Z)
DYNAMIC-VARS&gt; (bt:make-thread
               (lambda ()
                 (with-slots (slot1 slot2 slot3) *object*
                   (setf slot1 :i slot2 :j slot3 :k)
                   (print (list slot1 slot2 slot3)))))

#&lt;process &quot;Anonymous thread&quot; 0x7f76424c0240&gt;

(:I :J :K) 
DYNAMIC-VARS&gt; (with-slots (slot1 slot2 slot3) *object*
                (list slot1 slot2 slot3))
(:I :J :Z)
</code></pre>

<p><a id="orgd6a801a"></a></p>

<h1>What can we use it for?</h1>

<p>Now that we know how to define thread-local variables, we are left with a
question what can we use it for. Consider having a line-buffering stream. One
possible implementation could be sketched as:</p>

<pre><code>(defclass line-buffering-stream (fancy-stream)
  ((current-line :initform (make-adjustable-string)
                 :accessor current-line)
   (current-ink :initform +black+
                :accessor current-ink)))

(defmethod stream-write-char ((stream line-buffering-stream) char)
  (if (char= char #\newline)
      (terpri stream)
      (vector-push-extend char (current-line stream))))

(defmethod stream-terpri ((stream line-buffering-stream))
  (%put-line-on-screen (current-line stream) (current-ink stream))
  (setf (fill-pointer (current-line stream)) 0))
</code></pre>

<p>If this stream is shared between multiple threads, then even if individual
operations and <code>%PUT-LINE-ON-SCREEN</code> are thread-safe , we have a problem. For
example <code>FORMAT</code> writes are not usually atomic and individual lines are easily
corrupted. If we use custom colors, these are also a subject of race conditions.
The solution is as easy as making both slots thread-local. In that case the
buffered line is private to each thread and it is put on the screen atomically:</p>

<pre><code>(defclass line-buffering-stream (fancy-stream)
  ((current-line
    :initform (make-adjustable-string)
    :accessor current-line
    :dynamic :tls)
   (current-ink
    :initform +black+
    :accessor current-ink
    :dynamic :tls))
  (:metaclass class-with-dynamic-slots))
</code></pre>

<p>Technique is not limited to streams. It may benefit thread-safe drawing, request
processing, resource management and more. By subclassing <code>DYNAMIC-VARIABLE</code> we
could create also variables that are local to different objects than processes.</p>

<p>I hope that you've enjoyed reading this post as much as I had writing it. If you
are interested in a full standalone implementation, with tests and system
definitions, you may get it <a href="https://fossil.turtleware.eu/dynamic-vars" >here</a>. Cheers!</p>
 ]]></description> </item><item> <title>Dynamic Vars - The Empire Strikes Back</title> <link>/posts/Dynamic-Vars---The-Empire-Strikes-Back.html</link> <pubDate>2024-10-28</pubDate> <author>Daniel Kochmański</author> <guid isPermaLink="true">/posts/Dynamic-Vars---The-Empire-Strikes-Back.html</guid> <category><![CDATA[ lisp ]]></category><category><![CDATA[ mop ]]></category><category><![CDATA[ tutorial ]]></category> <description><![CDATA[ <h1>Table of Contents</h1>

<ol>
<li><a href="#orgbd5cbcb" >Thread Local storage exhausted</a></li>
<li><a href="#orgcf6145e" >The layer of indirection</a></li>
<li><a href="#org1c8b0e1" >I can fix her</a></li>
<li><a href="#org98d9497" >Let's write some tests!</a></li>
<li><a href="#org3a3756c" >Summary</a></li>
</ol>

<p><a id="orgbd5cbcb"></a></p>

<h1>Thread Local storage exhausted</h1>

<p>In the last post I've described a technique to use dynamic variables by value
instead of the name by utilizing the operator <code>PROGV</code>. Apparently it works fine
on all Common Lisp implementations I've tried except from <code>SBCL</code>, where the
number of thread local variables is by default limited to something below 4000.
To add salt to the injury, these variables are not garbage collected.</p>

<p>Try the following code to crash into <code>LDB</code>:</p>

<pre><code>(defun foo ()
  (loop for i from 0 below 4096 do
    (when (zerop (mod i 100))
      (print i))
    (progv (list (gensym)) (list 42)
      (values))))
(foo)
</code></pre>

<p>This renders our new technique not very practical given <code>SBCL</code> popularity. We
need to either abandon the idea or come up with a workaround.</p>

<p><a id="orgcf6145e"></a></p>

<h1>The layer of indirection</h1>

<p>Luckily for us we've already introduced a layer of indirection. Operators to
access dynamic variables are called <code>DLET</code>, <code>DSET</code> and <code>DREF</code>. This means, that
it is enough to provide a kludge implementation for <code>SBCL</code> with minimal changes
to the remaining code.</p>

<p>The old code works the same as previously except that instead of <code>SYMBOL-VALUE</code>
we use the accessor <code>DYNAMIC-VARIABLE-VALUE</code>, and the old call to <code>PROGV</code> is now
<code>DYNAMIC-VARIABLE-PROGV</code>. Moreover <code>DYNAMIC-EFFECTIVE-SLOT</code> used functions
<code>BOUNDP</code> and <code>MAKUNBOUND</code>, so we replace these with <code>DYNAMIC-VARIABLE-BOUND-P</code>
and <code>DYNAMIC-VARIABLE-MAKUNBOUND</code>. To abstract away things further we also
introduce the constructor <code>MAKE-DYNAMIC-VARIABLE</code></p>

<pre><code>(defpackage &quot;EU.TURTLEWARE.BLOG/DLET&quot;
  (:local-nicknames (&quot;MOP&quot; #+closer-mop &quot;C2MOP&quot;
                           #+(and (not closer-mop) ecl) &quot;MOP&quot;
                           #+(and (not closer-mop) ccl) &quot;CCL&quot;
                           #+(and (not closer-mop) sbcl) &quot;SB-MOP&quot;))
  (:use &quot;CL&quot;))
(in-package &quot;EU.TURTLEWARE.BLOG/DLET&quot;)

(eval-when (:compile-toplevel :execute :load-toplevel)
  (unless (member :bordeaux-threads *features*)
    (error &quot;Please load BORDEAUX-THREADS.&quot;))
  (when (member :sbcl *features*)
    (unless (member :fake-progv-kludge *features*)
      (format t &quot;~&amp;;; Using FAKE-PROGV-KLUDGE for SBCL.~%&quot;)
      (push :fake-progv-kludge *features*))))

(defmacro dlet (bindings &amp;body body)
  (flet ((pred (binding)
           (and (listp binding) (= 2 (length binding)))))
    (unless (every #'pred bindings)
      (error &quot;DLET: bindings must be lists of two values.~%~
                Invalid bindings:~%~{ ~s~%~}&quot; (remove-if #'pred bindings))))
  (loop for (var val) in bindings
        collect var into vars
        collect val into vals
        finally (return `(dynamic-variable-progv (list ,@vars) (list ,@vals)
                           ,@body))))

(defmacro dset (&amp;rest pairs)
  `(setf ,@(loop for (var val) on pairs by #'cddr
                 collect `(dref ,var)
                 collect val)))

(defmacro dref (variable)
  `(dynamic-variable-value ,variable))

;;; ...

(defmethod mop:slot-boundp-using-class
    ((class standard-class)
     object
     (slotd dynamic-effective-slot))
  (dynamic-variable-bound-p (slot-dvar object slotd)))

(defmethod mop:slot-makunbound-using-class
    ((class standard-class)
     object
     (slotd dynamic-effective-slot))
  (dynamic-variable-makunbound (slot-dvar object slotd)))
</code></pre>

<p>With these in place we can change the portable implementation to conform.</p>

<pre><code>#-fake-progv-kludge
(progn
  (defun make-dynamic-variable ()
    (gensym))

  (defun dynamic-variable-value (variable)
    (symbol-value variable))

  (defun (setf dynamic-variable-value) (value variable)
    (setf (symbol-value variable) value))

  (defun dynamic-variable-bound-p (variable)
    (boundp variable))

  (defun dynamic-variable-makunbound (variable)
    (makunbound variable))

  (defmacro dynamic-variable-progv (vars vals &amp;body body)
    `(progv ,vars ,vals ,@body)))
</code></pre>

<p><a id="org1c8b0e1"></a></p>

<h1>I can fix her</h1>

<p>The implementation for SBCL will mediate access to the dynamic variable value
with a synchronized hash table with weak keys. The current process is the key of
the hash table and the list of bindings is the value of the hash table. For
compatibility between implementations the top level value of the symbol will be
shared.</p>

<p>The variable <code>+FAKE-UNBOUND+</code> is the marker that signifies, that the variable
has no value. When the list of bindings is <code>EQ</code> to <code>+CELL-UNBOUND+</code>, then it
means that we should use the global value. We add new bindings by pushing to it.</p>

<pre><code>#+fake-progv-kludge
(progn
  (defvar +fake-unbound+ 'unbound)
  (defvar +cell-unbound+ '(no-binding))

  (defclass dynamic-variable ()
    ((tls-table
      :initform (make-hash-table :synchronized t :weakness :key)
      :reader dynamic-variable-tls-table)
     (top-value
      :initform +fake-unbound+
      :accessor dynamic-variable-top-value)))

  (defun make-dynamic-variable ()
    (make-instance 'dynamic-variable))

  (defun dynamic-variable-bindings (dvar)
    (let ((process (bt:current-thread))
          (tls-table (dynamic-variable-tls-table dvar)))
      (gethash process tls-table +cell-unbound+)))

  (defun (setf dynamic-variable-bindings) (value dvar)
    (let ((process (bt:current-thread))
          (tls-table (dynamic-variable-tls-table dvar)))
      (setf (gethash process tls-table +cell-unbound+) value))))
</code></pre>

<p>We define two readers for the variable value - one that simply reads the value,
and the other that signals an error if the variable is unbound. Writer for its
value either replaces the current binding, or if the value cell is unbound, then
we modify the top-level symbol value. We use the value <code>+FAKE-UNBOUND+</code> to check
whether the variable is bound and to make it unbound.</p>

<pre><code>#+fake-progv-kludge
(progn
  (defun %dynamic-variable-value (dvar)
    (let ((tls-binds (dynamic-variable-bindings dvar)))
      (if (eq tls-binds +cell-unbound+)
          (dynamic-variable-top-value dvar)
          (car tls-binds))))

  (defun dynamic-variable-value (dvar)
    (let ((tls-value (%dynamic-variable-value dvar)))
      (when (eq tls-value +fake-unbound+)
        (error 'unbound-variable :name &quot;(unnamed)&quot;))
      tls-value))

  (defun (setf dynamic-variable-value) (value dvar)
    (let ((tls-binds (dynamic-variable-bindings dvar)))
      (if (eq tls-binds +cell-unbound+)
          (setf (dynamic-variable-top-value dvar) value)
          (setf (car tls-binds) value))))

  (defun dynamic-variable-bound-p (dvar)
    (not (eq +fake-unbound+ (%dynamic-variable-value dvar))))

  (defun dynamic-variable-makunbound (dvar)
    (setf (dynamic-variable-value dvar) +fake-unbound+)))
</code></pre>

<p>Finally we define the operator to dynamically bind variables that behaves
similar to <code>PROGV</code>. Note that we <code>PUSH</code> and <code>POP</code> from the thread-local hash
table <code>DYNAMIC-VARIABLE-BINDINGS</code>, so no synchronization is necessary.</p>

<pre><code>#+fake-progv-kludge
(defmacro dynamic-variable-progv (vars vals &amp;body body)
  (let ((svars (gensym))
        (svals (gensym))
        (var (gensym))
        (val (gensym)))
    `(let ((,svars ,vars))
       (loop for ,svals = ,vals then (rest ,svals)
             for ,var in ,svars
             for ,val = (if ,svals (car ,svals) +fake-unbound+)
             do (push ,val (dynamic-variable-bindings ,var)))
       (unwind-protect (progn ,@body)
         (loop for ,var in ,svars
               do (pop (dynamic-variable-bindings ,var)))))))
</code></pre>

<p><a id="org98d9497"></a></p>

<h1>Let's write some tests!</h1>

<p>But of course, we are going to also write a test framework. It's short, I
promise. As a bonus point the API is compatibile with <code>fiveam</code>, so it is
possible to drop tests as is in the appropriate test suite.</p>

<pre><code>(defvar *all-tests* '())

(defun run-tests ()
  (dolist (test (reverse *all-tests*))
    (format *debug-io* &quot;Test ~a... &quot; test)
    (handler-case (funcall test)
      (serious-condition (c)
        (format *debug-io* &quot;Failed: ~a~%&quot; c))
      (:no-error (&amp;rest args)
        (declare (ignore args))
        (format *debug-io* &quot;Passed.~%&quot;)))))

(defmacro test (name &amp;body body)
  `(progn
     (pushnew ',name *all-tests*)
     (defun ,name () ,@body)))

(defmacro is (form)
  `(assert ,form))

(defmacro pass ())

(defmacro signals (condition form)
  `(is (block nil
         (handler-case ,form
           (,condition () (return t)))
         nil)))

(defmacro finishes (form)
  `(is (handler-case ,form
         (serious-condition (c)
           (declare (ignore c))
           nil)
         (:no-error (&amp;rest args)
           (declare (ignore args))
           t))))
</code></pre>

<p>Now let's get to tests. First we'll test our metaclass:</p>

<pre><code>(defclass dynamic-let.test-class ()
  ((slot1 :initarg :slot1 :dynamic nil :accessor slot1)
   (slot2 :initarg :slot2 :dynamic t   :accessor slot2)
   (slot3 :initarg :slot3              :accessor slot3))
  (:metaclass class-with-dynamic-slots))

(defparameter *dynamic-let.test-instance-1*
  (make-instance 'dynamic-let.test-class
                 :slot1 :a :slot2 :b :slot3 :c))

(defparameter *dynamic-let.test-instance-2*
  (make-instance 'dynamic-let.test-class
                 :slot1 :x :slot2 :y :slot3 :z))

(test dynamic-let.1
  (let ((o1 *dynamic-let.test-instance-1*)
        (o2 *dynamic-let.test-instance-2*))
    (with-slots (slot1 slot2 slot3) o1
      (is (eq :a slot1))
      (is (eq :b slot2))
      (is (eq :c slot3)))
    (with-slots (slot1 slot2 slot3) o2
      (is (eq :x slot1))
      (is (eq :y slot2))
      (is (eq :z slot3)))))

(test dynamic-let.2
  (let ((o1 *dynamic-let.test-instance-1*)
        (o2 *dynamic-let.test-instance-2*))
    (signals error (slot-dlet (((o1 'slot1) 1)) nil))
    (slot-dlet (((o1 'slot2) :k))
      (is (eq :k (slot-value o1 'slot2)))
      (is (eq :y (slot-value o2 'slot2))))))

(test dynamic-let.3
  (let ((o1 *dynamic-let.test-instance-1*)
        (exit nil)
        (fail nil))
    (flet ((make-runner (values)
             (lambda ()
               (slot-dlet (((o1 'slot2) :start))
                 (let ((value (slot2 o1)))
                   (unless (eq value :start)
                     (setf fail value)))
                 (loop until (eq exit t) do
                   (setf (slot2 o1) (elt values (random (length values))))
                   (let ((value (slot2 o1)))
                     (unless (member value values)
                       (setf fail value)
                       (setf exit t))))))))
      (let ((r1 (bt:make-thread (make-runner '(:k1 :k2))))
            (r2 (bt:make-thread (make-runner '(:k3 :k4))))
            (r3 (bt:make-thread (make-runner '(:k5 :k6)))))
        (sleep .1)
        (setf exit t)
        (map nil #'bt:join-thread (list r1 r2 r3))
        (is (eq (slot2 o1) :b))
        (is (null fail))))))
</code></pre>

<p>Then let's test the dynamic variable itself:</p>

<pre><code>(test dynamic-let.4
  &quot;Test basic dvar operators.&quot;
  (let ((dvar (make-dynamic-variable)))
    (is (eql 42 (dset dvar 42)))
    (is (eql 42 (dref dvar)))
    (ignore-errors
     (dlet ((dvar :x))
       (is (eql :x (dref dvar)))
       (error &quot;foo&quot;)))
    (is (eql 42 (dref dvar)))))

(test dynamic-let.5
  &quot;Test bound-p operator.&quot;
  (let ((dvar (make-dynamic-variable)))
    (is (not (dynamic-variable-bound-p dvar)))
    (dset dvar 15)
    (is (dynamic-variable-bound-p dvar))
    (dynamic-variable-makunbound dvar)
    (is (not (dynamic-variable-bound-p dvar)))))

(test dynamic-let.6
  &quot;Test makunbound operator.&quot;
  (let ((dvar (make-dynamic-variable)))
    (dset dvar t)
    (is (dynamic-variable-bound-p dvar))
    (finishes (dynamic-variable-makunbound dvar))
    (is (not (dynamic-variable-bound-p dvar)))))

(test dynamic-let.7
  &quot;Test locally bound-p operator.&quot;
  (let ((dvar (make-dynamic-variable)))
    (is (not (dynamic-variable-bound-p dvar)))
    (dlet ((dvar 15))
      (is (dynamic-variable-bound-p dvar)))
    (is (not (dynamic-variable-bound-p dvar)))))

(test dynamic-let.8
  &quot;Test locally unbound-p operator.&quot;
  (let ((dvar (make-dynamic-variable)))
    (dset dvar t)
    (is (dynamic-variable-bound-p dvar))
    (dlet ((dvar nil))
      (is (dynamic-variable-bound-p dvar))
      (finishes (dynamic-variable-makunbound dvar))
      (is (not (dynamic-variable-bound-p dvar))))
    (is (dynamic-variable-bound-p dvar))))

(test dynamic-let.9
  &quot;Stress test the implementation (see :FAKE-PROGV-KLUDGE).&quot;
  (finishes                              ; at the same time
    (let ((dvars (loop repeat 4096 collect (make-dynamic-variable))))
      ;; ensure tls variable
      (loop for v in dvars do
        (dlet ((v 1))))
      (loop for i from 0 below 4096
            for r = (random 4096)
            for v1 in dvars
            for v2 = (elt dvars r) do
              (when (zerop (mod i 64))
                (pass))
              (dlet ((v1 42)
                     (v2 43))
                (values))))))

(test dynamic-let.0
  &quot;Stress test the implementation (see :FAKE-PROGV-KLUDGE).&quot;
  (finishes                             ; can be gc-ed
    (loop for i from 0 below 4096 do
      (when (zerop (mod i 64))
        (pass))
      (dlet (((make-dynamic-variable) 42))
        (values)))))
</code></pre>

<p>All that is left is to test both dynamic variable implementations:</p>

<pre><code>BLOG/DLET&gt; (lisp-implementation-type)
&quot;ECL&quot;
BLOG/DLET&gt; (run-tests)
Test DYNAMIC-LET.1... Passed.
Test DYNAMIC-LET.2... Passed.
Test DYNAMIC-LET.3... Passed.
Test DYNAMIC-LET.4... Passed.
Test DYNAMIC-LET.5... Passed.
Test DYNAMIC-LET.6... Passed.
Test DYNAMIC-LET.7... Passed.
Test DYNAMIC-LET.8... Passed.
Test DYNAMIC-LET.9... Passed.
Test DYNAMIC-LET.0... Passed.
NIL
</code></pre>

<p>And with the kludge:</p>

<pre><code>BLOG/DLET&gt; (lisp-implementation-type)
&quot;SBCL&quot;
BLOG/DLET&gt; (run-tests)
Test DYNAMIC-LET.1... Passed.
Test DYNAMIC-LET.2... Passed.
Test DYNAMIC-LET.3... Passed.
Test DYNAMIC-LET.4... Passed.
Test DYNAMIC-LET.5... Passed.
Test DYNAMIC-LET.6... Passed.
Test DYNAMIC-LET.7... Passed.
Test DYNAMIC-LET.8... Passed.
Test DYNAMIC-LET.9... Passed.
Test DYNAMIC-LET.0... Passed.
NIL
</code></pre>

<p><a id="org3a3756c"></a></p>

<h1>Summary</h1>

<p>In this post we've made our implementation to work on SBCL even when there are
more than a few thousand dynamic variables. We've also added a simple test suite
that checks the basic behavior.</p>

<p>As it often happens, after achieving some goal we get greedy and achieve more.
That's the case here as well. In the next (and the last) post in this series
I'll explore the idea of adding truly thread-local variables without a shared
global value. This will be useful for lazily creating context on threads that
are outside of our control. We'll also generalize the implementation so it is
possible to subclass and implement ones own flavor of a dynamic variable.</p>
 ]]></description> </item><item> <title>Dynamic Vars - A New Hope</title> <link>/posts/Dynamic-Vars---A-New-Hope.html</link> <pubDate>2024-10-22</pubDate> <author>Daniel Kochmański</author> <guid isPermaLink="true">/posts/Dynamic-Vars---A-New-Hope.html</guid> <category><![CDATA[ lisp ]]></category><category><![CDATA[ mop ]]></category><category><![CDATA[ tutorial ]]></category> <description><![CDATA[ <h1>Table of Contents</h1>

<ol>
<li><a href="#org2c367f5" >Dynamic Bindings</a></li>
<li><a href="#org54a51ae" >The problem</a></li>
<li><a href="#orge9f1162" >The solution</a></li>
<li><a href="#org4dd5a75" >Dynamic slots</a></li>
<li><a href="#org3cc292f" >The context</a></li>
<li><a href="#org827046a" >Summary</a></li>
</ol>

<p><a id="org2c367f5"></a></p>

<h1>Dynamic Bindings</h1>

<p>Common Lisp has an important language feature called <code>dynamic binding</code>. It is
possible to rebind a dynamic variable somewhere on the call stack and downstream
functions will see that new value, and when the stack is unwound, the old value
is brought back.</p>

<p>While Common Lisp does not specify multi-threading, it seems to be a consensus
among various implementations that dynamic bindings are thread-local, allowing
for controlling the computing context in a safe way.</p>

<p>Before we start experiments, let's define a package to isolate our namespace:</p>

<pre><code>(defpackage &quot;EU.TURTLEWARE.BLOG/DLET&quot;
  (:local-nicknames (&quot;MOP&quot; #+closer-mop &quot;C2MOP&quot;
                           #+(and (not closer-mop) ecl) &quot;MOP&quot;
                           #+(and (not closer-mop) ccl) &quot;CCL&quot;
                           #+(and (not closer-mop) sbcl) &quot;SB-MOP&quot;))
  (:use &quot;CL&quot;))
(in-package &quot;EU.TURTLEWARE.BLOG/DLET&quot;)
</code></pre>

<p>Dynamic binding of variables is transparent to the programmer, because the
operator <code>LET</code> is used for both lexical and dynamic bindings. For example:</p>

<pre><code>(defvar *dynamic-variable* 42)

(defun test ()
  (let ((*dynamic-variable* 15)
        (lexical-variable 12))
    (lambda ()
      (print (cons *dynamic-variable* lexical-variable)))))

(funcall (test))
;;; (42 . 12)

(let ((*dynamic-variable* 'xx))
  (funcall (test)))
;;; (xx . 12)
</code></pre>

<p>Additionally the language specifies a special operator <code>PROGV</code> that gives the
programmer a control over the dynamic binding mechanism, by allowing passing the
dynamic variable by value instead of its name. Dynamic variables are represented
by symbols:</p>

<pre><code>(progv (list '*dynamic-variable*) (list 'zz)
  (funcall (test)))
;;; (zz . 12)
</code></pre>

<p><a id="org54a51ae"></a></p>

<h1>The problem</h1>

<p>Nowadays it is common to encapsulate the state in the instance of a class.
Sometimes that state is dynamic. It would be nice if we could use dynamic
binding to control it. That said slots are not variables, and if there are many
objects of the same class with different states, then using dynamic variables
defined with <code>DEFVAR</code> is not feasible.</p>

<p>Consider the following classes which we want to be thread-safe:</p>

<pre><code>(defgeneric call-with-ink (cont window ink))

(defclass window-1 ()
  ((ink :initform 'red :accessor ink)))

(defmethod call-with-ink (cont (win window-1) ink)
  (let ((old-ink (ink win)))
    (setf (ink win) ink)
    (unwind-protect (funcall cont)
      (setf (ink win) old-ink))))

(defclass window-2 ()
  ())

(defvar *ink* 'blue)
(defmethod ink ((window window-2)) *ink*)

(defmethod call-with-ink (cont (win window-2) ink)
  (let ((*ink* ink))
    (funcall cont)))
</code></pre>

<p>The first example is clearly not thread safe. If we access the <code>WINDOW-1</code>
instance from multiple threads, then they will overwrite a value of the slot
<code>INK</code>.</p>

<p>The second example is not good either, because when we have many instances of
<code>WINDOW-2</code> then they share the binding. Nesting <code>CALL-WITH-INK</code> will overwrite
the binding of another window.</p>

<p><a id="orge9f1162"></a></p>

<h1>The solution</h1>

<p>The solution is to use <code>PROGV</code>:</p>

<pre><code>(defclass window-3 ()
  ((ink :initform (gensym))))

(defmethod initialize-instance :after ((win window-3) &amp;key)
  (setf (symbol-value (slot-value win 'ink)) 'red))

(defmethod call-with-ink (cont (win window-3) ink)
  (progv (list (slot-value win 'ink)) (list ink)
    (funcall cont)))
</code></pre>

<p>This way each instance has its own dynamic variable that may be rebound with a
designated operator <code>CALL-WITH-INK</code>. It is thread-safe and private. We may add
some syntactic sugar so it is more similar to let:</p>

<pre><code>(defmacro dlet (bindings &amp;body body)
  (loop for (var val) in bindings
        collect var into vars
        collect val into vals
        finally (return `(progv (list ,@vars) (list ,@vals)
                           ,@body))))

(defmacro dset (&amp;rest pairs)
  `(setf ,@(loop for (var val) on pairs by #'cddr
                 collect `(symbol-value ,var)
                 collect val)))

(defmacro dref (variable)
  `(symbol-value ,variable))
</code></pre>

<p><a id="org4dd5a75"></a></p>

<h1>Dynamic slots</h1>

<p>While meta-classes are not easily composable, it is worth noting that we can mold
it better into the language by specifying that slot itself has a dynamic value.
This way CLOS aficionados will have a new tool in their arsenal.</p>

<p>The approach we'll take is that a fresh symbol is stored as the value of each
instance-allocated slot, and then accessors for the slot value will use these
symbols as a dynamic variable. Here are low-level accessors:</p>

<pre><code>;;; Accessing and binding symbols behind the slot. We don't use SLOT-VALUE,
;;; because it will return the _value_ of the dynamic variable, and not the
;;; variable itself.
(defun slot-dvar (object slotd)
  (mop:standard-instance-access
   object (mop:slot-definition-location slotd)))

(defun slot-dvar* (object slot-name)
  (let* ((class (class-of object))
         (slotd (find slot-name (mop:class-slots class)
                      :key #'mop:slot-definition-name)))
    (slot-dvar object slotd)))

(defmacro slot-dlet (bindings &amp;body body)
  `(dlet ,(loop for ((object slot-name) val) in bindings
                 collect `((slot-dvar* ,object ,slot-name) ,val))
     ,@body))
</code></pre>

<p>Now we'll define the meta-class. We need that to specialize functions responsible
for processing slot definitions and the instance allocation. Notice, that we
make use of a kludge to communicate between <code>COMPUTE-EFFECTIVE-SLOT-DEFINITION</code>
and <code>EFFECTIVE-SLOT-DEFINITION-CLASS</code> &#x2013; this is because the latter has no
access to the direct slot definitions.</p>

<pre><code>;;; The metaclass CLASS-WITH-DYNAMIC-SLOTS specifies alternative effective slot
;;; definitions for slots with an initarg :dynamic.
(defclass class-with-dynamic-slots (standard-class) ())

;;; Class with dynamic slots may be subclasses of the standard class.
(defmethod mop:validate-superclass ((class class-with-dynamic-slots)
                                    (super standard-class))
  t)

;;; When allocating the instance we initialize all slots to a fresh symbol that
;;; represents the dynamic variable.
(defmethod allocate-instance ((class class-with-dynamic-slots) &amp;rest initargs)
  (declare (ignore initargs))
  (let ((object (call-next-method)))
    (loop for slotd in (mop:class-slots class)
          when (typep slotd 'dynamic-effective-slot) do
            (setf (mop:standard-instance-access
                   object
                   (mop:slot-definition-location slotd))
                  (gensym (string (mop:slot-definition-name slotd)))))
    object))

;;; To improve potential composability of CLASS-WITH-DYNAMIC-SLOTS with other
;;; metaclasses we treat specially only slots that has :DYNAMIC in initargs,
;;; otherwise we call the next method.
(defmethod mop:direct-slot-definition-class
    ((class class-with-dynamic-slots) &amp;rest initargs)
  (loop for (key val) on initargs by #'cddr
        when (eq key :dynamic)
          do (return-from mop:direct-slot-definition-class
               (find-class 'dynamic-direct-slot)))
  (call-next-method))

;;; The metaobject protocol did not specify an elegant way to communicate
;;; between the direct slot definition and the effective slot definition.
;;; Luckily we have dynamic bindings! :-)
(defvar *kludge/mop-deficiency/dynamic-slot-p* nil)
(defmethod mop:compute-effective-slot-definition
    ((class class-with-dynamic-slots)
     name
     direct-slotds)
  (if (typep (first direct-slotds) 'dynamic-direct-slot)
      (let* ((*kludge/mop-deficiency/dynamic-slot-p* t))
        (call-next-method))
      (call-next-method)))

(defmethod mop:effective-slot-definition-class
    ((class class-with-dynamic-slots) &amp;rest initargs)
  (declare (ignore initargs))
  (if *kludge/mop-deficiency/dynamic-slot-p*
      (find-class 'dynamic-effective-slot)
      (call-next-method)))
</code></pre>

<p>Finally we define a direct and an effective slot classes, and specialize slot
accessors that are invoked by the instance accessors.</p>

<pre><code>;;; There is a considerable boilerplate involving customizing slots.
;;;
;;; - direct slot definition: local to a single defclass form
;;;
;;; - effective slot definition: combination of all direct slots with the same
;;;   name in the class and its superclasses
;;;
(defclass dynamic-direct-slot (mop:standard-direct-slot-definition)
  ((dynamic :initform nil :initarg :dynamic :reader dynamic-slot-p)))

;;; DYNAMIC-EFFECTIVE-SLOT is implemented to return as slot-value values of the
;;; dynamic variable that is stored with the instance.
;;;
;;; It would be nice if we could specify :ALLOCATION :DYNAMIC for the slot, but
;;; then STANDARD-INSTANCE-ACCESS would go belly up. We could make a clever
;;; workaround, but who cares?
(defclass dynamic-effective-slot (mop:standard-effective-slot-definition)
  ())

(defmethod mop:slot-value-using-class
    ((class class-with-dynamic-slots)
     object
     (slotd dynamic-effective-slot))
  (dref (slot-dvar object slotd)))

(defmethod (setf mop:slot-value-using-class)
    (new-value
     (class class-with-dynamic-slots)
     object
     (slotd dynamic-effective-slot))
  (dset (slot-dvar object slotd) new-value))

(defmethod mop:slot-boundp-using-class
  ((class class-with-dynamic-slots)
   object
   (slotd dynamic-effective-slot))
  (boundp (slot-dvar object slotd)))

(defmethod mop:slot-makunbound-using-class
  ((class class-with-dynamic-slots)
   object
   (slotd dynamic-effective-slot))
  (makunbound (slot-dvar object slotd)))
</code></pre>

<p>With this, we can finally define a class with slots that have dynamic
values. What's more, we may bind them like dynamic variables.</p>

<pre><code>;;; Let there be light.
(defclass window-4 ()
  ((ink :initform 'red :dynamic t :accessor ink)
   (normal :initform 'normal :accessor normal))
  (:metaclass class-with-dynamic-slots))

(let ((object (make-instance 'window-4)))
  (slot-dlet (((object 'ink) 15))
    (print (ink object)))
  (print (ink object)))
</code></pre>

<p><a href="https://github.com/pcostanza/contextl" >ContextL</a> provides a similar solution with dynamic slots, although it provides
much more, like layered classes. This example is much more self-contained.</p>

<p><a id="org3cc292f"></a></p>

<h1>The context</h1>

<p>Lately I'm working on the repaint queue for McCLIM. While doing so I've decided
to make stream operations thread-safe, so it is possible to draw on the stream
and write to it from arbitrary thread asynchronously. The access to the output
record history needs to be clearly locked, so that may be solved by the mutex.
Graphics state is another story, consider the following functions running from
separate threads:</p>

<pre><code>(defun team-red ()
  (with-drawing-options (stream :ink +dark-red+)
    (loop for i from 0 below 50000 do
      (write-string (format nil &quot;XXX: ~5d~%&quot; i) stream))))

(defun team-blue ()
  (with-drawing-options (stream :ink +dark-blue+)
    (loop for i from 0 below 50000 do
      (write-string (format nil &quot;YYY: ~5d~%&quot; i) stream))))

(defun team-pink ()
  (with-drawing-options (stream :ink +deep-pink+)
    (loop for i from 0 below 25000 do
      (case (random 2)
        (0 (draw-rectangle* stream 200 (* i 100) 250 (+ (* i 100) 50)))
        (1 (draw-circle* stream 225 (+ (* i 100) 25) 25))))))

(defun gonow (stream)
  (window-clear stream)
  (time (let ((a (clim-sys:make-process #'team-red))
              (b (clim-sys:make-process #'team-blue))
              (c (clim-sys:make-process #'team-grue)))
          (bt:join-thread a)
          (bt:join-thread b)
          (bt:join-thread c)
          (format stream &quot;done!~%&quot;)))  )
</code></pre>

<p>Operations like <code>WRITE-STRING</code> and <code>DRAW-RECTANGLE</code> can be implemented by
holding a lock over the shared resource without much disruption. The drawing
color on the other hand is set outside of the loop, so if we had locked the
graphics state with a lock, then these functions would be serialized despite
being called from different processes. The solution to this problem is to make
graphics context a dynamic slot that is accessed with <code>WITH-DRAWING-OPTIONS</code>.</p>

<p><a id="org827046a"></a></p>

<h1>Summary</h1>

<p>I hope that I've convinced you that dynamic variables are cool (I'm sure that
majority of readers here are already convinced), and that dynamic slots are even
cooler :-). Watch forward to the upcoming McCLIM release!</p>

<p>If you like technical writeups like this, please consider supporting me on
<a href="https://www.patreon.com/c/jackdaniel_kochmanski" >Patreon</a>.</p>
 ]]></description> </item><item> <title>Writing an ad hoc GUI for Coleslaw</title> <link>/posts/Writing-an-ad-hoc-GUI-for-Coleslaw.html</link> <pubDate>2024-01-30</pubDate> <author>Daniel Kochmański</author> <guid isPermaLink="true">/posts/Writing-an-ad-hoc-GUI-for-Coleslaw.html</guid> <category><![CDATA[ lisp ]]></category><category><![CDATA[ mcclim ]]></category><category><![CDATA[ tutorial ]]></category> <description><![CDATA[ <h1>Table of Contents</h1>

<ol>
<li><a href="#org8ed53fb" >Preliminary steps</a></li>
<li><a href="#orgbc42e8f" >Embracing the chaos</a></li>
<li><a href="#orgedf2164" >Presentations</a></li>
<li><a href="#org512c081" >Managing a blog collection</a></li>
<li><a href="#org6e7294c" >Managing a blog instance</a></li>
<li><a href="#orga3e9312" >Big ball of mud</a></li>
<li><a href="#orgbcf2313" >Closing thoughts</a></li>
</ol>

<p><a href="https://github.com/coleslaw-org/coleslaw" >Coleslaw</a> is a &quot;Flexible Lisp Blogware&quot;. It is a program that I'm using to manage
my blogs and allows for an offline blog compilation. The functionality of the
website may be extended with plugins and the visual appearance is defined by
configurable themes.</p>

<p>Its design is straightforward (if not a bit messy), so it is a good candidate to
show how to slap a CLIM interface on top of existing software. The integration
will be very shallow to not encroach into Coleslaw responsibilities, yet deep
enough to provide a convenience utility over the library.</p>

<p><a id="org8ed53fb"></a></p>

<h1>Preliminary steps</h1>

<p>In this post we will use a few dependencies. Of course one of them is <code>mcclim</code>.
Please make sure that you are using an up-to-date version; i.e clone it from the
<a href="https://codeberg.org/McCLIM/McCLIM" >repository</a> to <code>~/quicklisp/local-projects</code>. There are other dependencies too.
Load them all in the REPL with:</p>

<pre><code>(ql:quickload '(coleslaw-cli cl-fad alexandria local-time
                mcclim clouseau hunchentoot)
              :verbose t)
</code></pre>

<p>The whole program described in this tutorial is defined in a single package:</p>

<pre><code>(defpackage &quot;COLESLAW-GUI&quot;
  (:use &quot;CLIM-LISP&quot;))
(in-package &quot;COLESLAW-GUI&quot;)
</code></pre>

<p>We are good to go now.</p>

<p><a id="orgbc42e8f"></a></p>

<h1>Embracing the chaos</h1>

<p>The README.md in the project's repository mentions a few commands that may be
invoked from the command line and from the lisp REPL. What they have in common
is that they assume, that the blog resides in the current working directory.
Here we are going to introduce a macro that estabilishes a necessary context:</p>

<pre><code>(defmacro with-current-directory ((path value) &amp;body body)
  `(let* ((,path (cl-fad:pathname-as-directory ,value))
          (*default-pathname-defaults* ,path))
     (ensure-directories-exist ,path)
     (uiop:chdir ,path)
     ,@body))
</code></pre>

<p>Moreover Coleslaw assumes that only one blog will be loaded during its lifetime
and many objects are treated as singletons. We will embrace this chaos and
provide a macro that estabilishes an appropriate environment for a blog. The key
to each environment is its directory pathname:</p>

<pre><code>;;; Allow for passing &quot;env&quot; here.
(defun blog-key (blog)
  (etypecase blog
    (null nil)
    (cons (coleslaw:repo-dir (first blog)))
    (coleslaw::blog (coleslaw:repo-dir blog))))

(defun blog () coleslaw:*config*)
(defun site () coleslaw::*site*)

(defun make-null-env ()
  (list nil (make-hash-table :test #'equal)))

(defun copy-blog-env ()
  (list coleslaw:*config*
        coleslaw::*site*))

(defun load-blog-env (env)
  (destructuring-bind (blog site)
      (or env (make-null-env))
    (setf coleslaw:*config* blog
          coleslaw::*site* site)
    ;; Populates *ALL-TAGS* and *ALL-MONTHS* using *SITE*.
    (coleslaw::update-content-metadata)))

(defun save-blog-env (table)
  (when table
    (setf (gethash (blog-key coleslaw:*config*) table)
          (copy-blog-env))))

(defmacro with-blog-env ((env table) &amp;body body)
  `(let (coleslaw:*config*
         coleslaw::*site*)
     (load-blog-env ,env)
     (multiple-value-prog1 (progn ,@body)
       (save-blog-env ,table))))
</code></pre>

<p><a id="orgedf2164"></a></p>

<h1>Presentations</h1>

<p>First we will define presentation types, so we can associate them with objects
on the screen. The blog environment is composed of a pair:</p>

<ul>
<li><p><strong>a blog:</strong> an instance of the class <code>coleslaw::blog</code></p></li>
<li><p><strong>a site:</strong> a hash table that contains posts</p>

<p>(clim:define-presentation-type coleslaw::blog ()
  :description &quot;(Configuration)&quot;)</p>

<p>(clim:define-presentation-type blog-env ()
  :description &quot;(Blog)&quot;)</p></li>
</ul>

<p>Presentation types are like types denoted by classes, but with a twist - they
may be additionally parametrized; i.e <code>(INTEGER 3)</code> is a presentation
type. There are also an abstract presentation types that are not tied to a single
class. For example we may have presentation types &quot;red team&quot; and &quot;blue team&quot;,
where some arbitrary objects are presented as one or the another.</p>

<p>The presentation method <code>present</code> is used to associate the object with the
presentation type and put it on the screen as the presentation. In other words
the presentation is a pair <code>(object type)</code>. The method specializes arguments:</p>

<ul>
<li><strong>object:</strong> most notably the object class, sometimes left unspecialized</li>
<li><strong>type:</strong> obligatory specialization to the presentation type (may be abstract)</li>
<li><strong>stream:</strong> typically left unspecialized, but may be utilized for serialization</li>
<li><strong>view:</strong> customizes how the object is presented depending on the local context</li>
</ul>

<p>The most primitive view is the textual view. Methods specializing to it should
treat the stream as if it handles only text, so the representation should be a
string. Note that presentations may be nested, like in our case:</p>

<pre><code>(clim:define-presentation-method clim:present
    (self (type coleslaw::blog) stream (view clim:textual-view) &amp;key acceptably for-context-type)
  (declare (ignore view acceptably for-context-type))
  (format stream &quot;~a&quot; (blog-key self)))

(clim:define-presentation-method clim:present
    (env (type blog-env) stream (view clim:textual-view) &amp;key acceptably for-context-type)
  (declare (ignore acceptably for-context-type))
  (with-blog-env (env nil)
    (princ &quot;[&quot; stream)
    (clim:present (blog) 'coleslaw::blog :stream stream :view view)
    (princ &quot;]&quot; stream)))
</code></pre>

<p><a id="org512c081"></a></p>

<h1>Managing a blog collection</h1>

<p>The system <code>coleslaw-cli</code> that is bundled with <code>coleslaw</code> defines commands that
allow for creating a blog, adding (stub) post files to it, compiling the blog to
the staging area and deploying the blog using plugins.</p>

<p>We are going to extend this set of operations to allow working with a collection
of blogs. Since we are not barbarians, we are going to encapsulate the state in
the application frame, and not in a global variable.</p>

<pre><code>(clim:define-application-frame coleslaw-cli ()
  ((envs :initform (make-hash-table :test #'equal) :reader envs)))
</code></pre>

<p>Adding new blogs to the collection is a result of opening them or creating new
ones. Both operations require for the program to operate in a target directory:</p>

<pre><code>(clim:define-command (com-open-blog :name t :command-table coleslaw-cli)
    ((directory 'pathname))
  (clim:with-application-frame (frame)
    (with-current-directory (path directory)
      (with-blog-env (nil (envs frame))
        (format *query-io* &quot;Opening a blog in ~s.~%&quot; path)
        (coleslaw::load-config path)))))

(clim:define-command (com-make-blog :name t :command-table coleslaw-cli)
    ((directory 'pathname))
  (clim:with-application-frame (frame)
    (with-current-directory (path directory)
      (with-blog-env (nil (envs frame))
        (format *query-io* &quot;Creating a new blog in ~s. &quot; path)
        (coleslaw-cli:setup)
        (coleslaw::load-config path)))))
</code></pre>

<p>We need a command to list loaded blogs. All remaining operations will specialize
to presentation types <code>blog-env</code> and <code>coleslaw::blog</code>, so we will present them
with the function <code>present</code>:</p>

<pre><code>(clim:define-command (com-list-blogs :name t :command-table coleslaw-cli)
    ()
  (clim:with-application-frame (frame)
    (dolist (env (alexandria:hash-table-values (envs frame)))
      (clim:present env 'blog-env :stream (clim:frame-query-io frame)
                                  :single-box t)
      (terpri (clim:frame-query-io frame)))))
</code></pre>

<p>For completness we need a command that will remove a blog from the collection.</p>

<pre><code>(clim:define-command (com-close-blog :name t :command-table coleslaw-cli)
    ((self 'blog-env))
  (clim:with-application-frame (frame)
    (remhash (blog-key self) (envs frame))))
</code></pre>

<p>Finally there are two very important commands that compile the blog. Note that
both commands will fail if there are no posts in the blog (coleslaw behavior).</p>

<pre><code>(clim:define-command (com-stage-blog :name t :command-table coleslaw-cli)
    ((self 'blog-env))
  (clim:with-application-frame (frame)
    (with-current-directory (path (blog-key self))
      (with-blog-env (self (envs frame))
        (format *query-io* &quot;Staging the blog from ~s. &quot; path)
        (coleslaw-cli:stage)))))

(clim:define-command (com-deploy-blog :name t :command-table coleslaw-cli)
    ((self 'blog-env))
  (clim:with-application-frame (frame)
    (with-current-directory (path (blog-key self))
      (with-blog-env (self (envs frame))
        (format *query-io* &quot;Deploying the blog from ~s. &quot; path)
        (coleslaw-cli:deploy)))))
</code></pre>

<p>Additionally we define a few convenience commands:</p>

<ul>
<li>creating test data</li>
<li>clearing the screen</li>
</ul>

<pre><code>(clim:define-command (com-spam-blogs :name t :command-table coleslaw-cli)
    ()
  (dotimes (i 8)
    (let ((d (format nil "/tmp/blogs/specimen-~2,'0d/" i)))
      (if (probe-file d)
          (com-open-blog d)
          (with-current-directory (path d)
            (com-make-blog d)
            ;; Without any content "Stage" and "Deploy" will fail.
            (coleslaw-cli:new))))))

(clim:define-command (com-clear :name t :command-table coleslaw-cli)
    ()
  (clim:with-application-frame (frame)
    (clim:window-clear (clim:frame-query-io frame))))</code></pre>

<p>Now to execute a command on the blog we may type the command name and select an
element from the list (with a pointer). We want also to allow the user to
click on the blog with the right pointer button and select the operation without
explicitly typing the command, so we define presentation to command translators:</p>

<pre><code>(macrolet ((def (name command short-description long-description)
             `(clim:define-presentation-to-command-translator ,name
                  (blog-env ,command coleslaw-cli
                   :gesture nil
                   :documentation ,short-description
                   :pointer-documentation ,long-description)
                  (self)
                `(,self))))
  (def trn-close-blog  com-close-blog  &quot;Close&quot;  &quot;Remove blog from collection&quot;)
  (def trn-stage-blog  com-stage-blog  &quot;Stage&quot;  &quot;Compile blog to staging area&quot;)
  (def trn-deploy-blog com-deploy-blog &quot;Deploy&quot; &quot;Compile blog to production&quot;))
</code></pre>

<p>Moreover we'd like to be able to type the blog from the keyboard, so we define a
presentation method <code>accept</code> that matches the blog against loaded ones.</p>

<pre><code>(clim:define-presentation-method clim:accept
    ((type blog-env) stream (view clim:textual-view) &amp;rest args)
  (declare (ignore args))
  (clim:with-application-frame (frame)
    (clim:completing-from-suggestions (stream)
      (maphash (lambda (key val)
                 (clim:suggest (namestring key) val))
               (envs frame)))))
</code></pre>

<p>This concludes our command line blog manager. We've mentioned the following
topics:</p>

<ul>
<li><strong>application frame:</strong> defines the dynamic context of the application</li>
<li><strong>command table:</strong> defines available commands and translators</li>
<li><strong>presentation types:</strong> specify ontologies that may be shared among programs</li>
<li><strong>presentation methods:</strong> specify interactions like <code>present</code> and <code>accept</code></li>
</ul>

<p><video controls>
  <source src="/static/video/coleslaw/coleslaw-cli.webm" type="video/webm">
</video></p>

<p><a id="org6e7294c"></a></p>

<h1>Managing a blog instance</h1>

<p>Until now we've been working with the interactor and the textual view. Focusing
first on presentation types and commands is good, because it captures an essence
of the application interface and delays distracting stuff like visuals. Now, to
make this post more appealing (less appalling?), we will extend the application
with additional functionality.</p>

<p>The display function is responsible for presenting content on the application
stream. It may be anything really, but we will defer it to a method PRESENT
specialized to the frame itself. That's the purest approach. We also define a
few utilities for later.</p>

<pre><code>(defun display (object stream)
  (clim:present object (clim:presentation-type-of object) :stream stream))

(defun present* (object stream)
  (clim:present object (clim:presentation-type-of object) :stream stream))

(defmacro dohash (((key val) hash &amp;optional result) &amp;body body)
  (let ((cont (gensym)))
    `(flet ((,cont (,key ,val) ,@body))
       (declare (dynamic-extent (function ,cont)))
       (maphash (function ,cont) ,hash)
       ,result)))

(defun format-today ()
  (local-time:format-timestring nil (local-time:now)
                                :format '((:year 4) &quot;-&quot; (:month 2) &quot;-&quot; (:day 2) &quot;-&quot;
                                          (:hour 2) &quot;-&quot; (:min 2) &quot;-&quot; (:sec 2))))
</code></pre>

<p>Our application frame will feature graphics and other fluff to cater to people
who are into this kind of thing. To do that we define a separate view class that
extends the <code>textual-view</code>. While we are technically subclassing it, this is not
a semantically correct description. In reality we are extending the class with
non-textual capabilities. If you were looking for CLOS conceptual limits, then
here you have one.</p>

<pre><code>;;; KLUDGE: FANCY-VIEW extends (not specializes) the TEXTUAL-VIEW.
(defclass fancy-view (clim:textual-view) ())
(defvar +fancy-view+ (make-instance 'fancy-view))
</code></pre>

<p>Finally the application frame definition. It inherits from <code>coleslaw-cli</code> and
adds a new application pane to show the frame state.</p>

<pre><code>(clim:define-application-frame coleslaw-gui (coleslaw-cli)
  ((current-blog :initform nil :accessor current-blog))
  (:command-table (coleslaw-gui :inherit-from (coleslaw-cli)))
  (:reinitialize-frames t)
  (:panes (app :application :display-function 'display :default-view +fancy-view+
               :text-margins '(:left 20 :top 10))
          (int :interactor)))
</code></pre>

<p>Now we define new commands to load content, select a loaded blog and create a
new blog. Loading the content is the operation that walks directories and adds
found resources to the model.</p>

<pre><code>(clim:define-command (com-update :command-table coleslaw-gui)
    ((self 'blog-env))
  (clim:with-application-frame (frame)
    (with-current-directory (path (blog-key self))
      (with-blog-env (self (envs frame))
        ;; This function removes content from *site* before adding it back.
        (coleslaw::load-content)))))

(clim:define-command (com-select :command-table coleslaw-gui)
    ((self 'blog-env))
  (clim:with-application-frame (frame)
    (setf (current-blog frame) self)
    (com-update self)))

(clim:define-command (com-create :name t :command-table coleslaw-cli)
    ((self 'blog-env)
     (type 'string :default &quot;post&quot;)
     (name 'string :default (format-today)))
  (clim:with-application-frame (frame)
    (with-current-directory (path (blog-key self))
      (with-blog-env (self (envs frame))
        ;; This function removes content from *site* before adding it back.
        (coleslaw-cli:new type name)
        (com-update self)))))

(macrolet ((def (name command gesture short-description long-description)
             `(clim:define-presentation-to-command-translator ,name
                  (blog-env ,command coleslaw-gui
                   :gesture ,gesture
                   :documentation ,short-description
                   :pointer-documentation ,long-description)
                  (self)
                `(,self))))
  (def trn-update com-update nil     &quot;Update&quot;  &quot;Update blog from disk&quot;)
  (def trn-select com-select :select &quot;Select&quot;  &quot;Show blog details&quot;)
  (def trn-create com-create nil     &quot;Create&quot;  &quot;Create new content&quot;))
</code></pre>

<p>The implementation of the present method specializes to <code>fancy-view</code>. First we
show the list of opened blogs (using the textual view), and then we show the
selected blog. Rendering of the current blog is defered to another <code>present</code>
method.</p>

<p>The current blog will show the same content as it is presented on the list
above, until we define a specialized method. Note that we present it so it is
not sensitive to pointer clicks. This is to avoid unnecessary noise.</p>

<pre><code>(clim:define-presentation-method clim:present
    ((frame coleslaw-gui) (type coleslaw-gui) stream (view fancy-view) &amp;rest args)
  (declare (ignore args))
  (clim:formatting-item-list (stream)
   (dohash ((dir env) (envs frame))
     (declare (ignore dir))
     (clim:formatting-cell (stream)
       (clim:with-drawing-options (stream :ink (if (eql env (current-blog frame))
                                                   clim:+dark-red+
                                                   clim:+foreground-ink+))
         (clim:present env 'blog-env :stream stream :view clim:+textual-view+ :single-box t)))))
  (terpri stream)
  (clim:present (current-blog frame) 'blog-env :stream stream :view view
                                               :sensitive nil
                                               :allow-sensitive-inferiors t))
</code></pre>

<p>Presenting the current blog will be implemented as follows:</p>

<ol>
<li>Show the blog title &#x2013; header text style</li>
<li>Show available commands &#x2013; deliberely goofy icons</li>
<li>Show the blog content &#x2013; defered to the next method</li>
</ol>

<pre><code>(clim:define-presentation-type coleslaw::index ()
  :description "(Index)")

(clim:define-presentation-type site ()
  :description "(Site)")

(clim:define-presentation-type post ()
  :description "(Post)")

(clim:define-presentation-type post ()
  :description "(Page)")

(defun gap-the-gap (stream command label color)
  (clim:with-output-as-presentation (stream command '(clim:command :command-table coleslaw-gui))
    (clim:with-room-for-graphics (stream :first-quadrant nil)
      (clim:draw-circle* stream 0 0 40 :ink clim:+dark-red+ :filled nil :line-thickness 20)
      (clim:surrounding-output-with-border (stream :filled t :ink color)
        (clim:draw-text* stream label 0 0 :align-x :center :align-y :center
                                          :text-size :small
                                          :text-family :fix
                                          :ink clim:+white+)))))

(clim:define-presentation-method clim:present
    ((self cons) (type blog-env) stream (view fancy-view) &rest args)
  (declare (ignore args))
  (clim:with-application-frame (frame)
    (with-blog-env (self (envs frame))
      ;; Blog title
      (clim:with-text-style (stream (clim:make-text-style :serif :bold :large))
        (format stream "~a" (coleslaw:title (blog))))
      (terpri stream)
      ;; Update the blog bleeper
      (gap-the-gap stream `(com-update ,self) "Mind the gap!" clim:+dark-blue+)
      (princ " " stream)
      (gap-the-gap stream `(com-create ,self "post" ,(format-today)) "Fill the gap!" clim:+dark-green+)
      (princ " " stream)
      (gap-the-gap stream `(com-create ,self "page" ,(format-today)) "Keep the gap!" clim:+dark-red+)
      (terpri stream)
      ;; The content
      (clim:present (site) 'site :stream stream :view view))))

(clim:define-presentation-method clim:present
    (self (type site) stream (view fancy-view) &rest args)
  (declare (ignore args))
  (clim:formatting-table (stream)
   (dohash ((key val) self)
     (clim:formatting-row (stream)
       (clim:formatting-cell (stream) (present* key stream))
       (clim:formatting-cell (stream) (present* val stream))))))</code></pre>

<p>The discovered content is stored in a hash table. Keys are URL addresses and
values are content objects: posts, rss feeds, tag feeds and indexes. Values are
presented, so these presentations may be selected with a pointer when the input
context matches. For example we may invoke the inspector or a file editor:</p>

<pre><code>(clim:define-presentation-action act-open-content
    (coleslaw::content nil coleslaw-gui
     :documentation &quot;Open file&quot;
     :pointer-documentation &quot;Open the content file&quot;)
    (object)
  (uiop:launch-program (format nil &quot;xdg-open ~a&quot; (coleslaw::content-file object))))

(clim:define-presentation-action act-kill-content
    (coleslaw::content nil coleslaw-gui
     :documentation &quot;Kill file&quot;
     :pointer-documentation &quot;Kill the content file&quot;)
    (object)
  (clim:with-application-frame (frame)
    (with-current-directory (dir (blog-key (current-blog frame)))
      (uiop:launch-program (format nil &quot;rm ~a&quot; (coleslaw::content-file object))))
    (clim:execute-frame-command frame `(com-update ,(current-blog frame)))))

(clim:define-presentation-action act-inspect
    ((or coleslaw::blog coleslaw::content coleslaw::feed coleslaw::index) nil coleslaw-gui
     :gesture nil
     :documentation &quot;Inspect content&quot;
     :pointer-documentation &quot;Inspect site content&quot;)
  (object)
  (clouseau:inspect object :new-process t))
</code></pre>

<p>A difference between actions and commands is that actions are not expected to
change the internal model, so they don't progress the display loop. Now we may
click on a post and the default program that opens the file will be launched.
We may also right-click on the content value and inspect it with clouseau.</p>

<p>In this section I've mentioned the following topics:</p>

<ul>
<li>the textual view may be extended with graphical capabilities (i.e colors)</li>
<li>display function is a function that creates presentations on the stream</li>
<li>presentation translators may be used to call a command from a presentation</li>
<li>presentation method present may be nested inside another one</li>
<li>presentation types are used as specializers in presentation methods</li>
<li>it is possible to present on the stream a command along with arguments</li>
<li>presentation actions, unlike commands, are executed immedietely</li>
</ul>

<p><video controls>
  <source src="/static/video/coleslaw/coleslaw-gui.webm" type="video/webm">
</video></p>

<p><a id="orga3e9312"></a></p>

<h1>Big ball of mud</h1>

<p>Previously we've extended the application by specifying a new display function.
Now we will extend it further by adding a web server to preview a blog.</p>

<pre><code>(clim:define-application-frame durk (coleslaw-gui)
  ((acceptor :initarg :acceptor :accessor acceptor))
  (:panes
   (app :application :display-function 'display :default-view +fancy-view+)
   (int :interactor :height 100))
  (:reinitialize-frames t)
  (:command-table (durk :inherit-from (coleslaw-gui)))
  (:default-initargs :acceptor nil))

;;; We could enable and disable commands by calilng (SETF CLIM:COMMAND-ENABLED).
(defmethod clim:command-enabled (name (frame durk))
  (case name
    (com-stop-acceptor (hunchentoot:started-p (acceptor frame)))
    (com-start-acceptor (not (hunchentoot:started-p (acceptor frame))))
    (otherwise (call-next-method))))

(defmethod clim:adopt-frame :after (fm (self durk))
  (format *debug-io* &quot;Booting up.~%&quot;)
  (setf (acceptor self) (make-instance 'hunchentoot:easy-acceptor :port 4242))
  (setf hunchentoot:*dispatch-table*
        (list (hunchentoot:create-static-file-dispatcher-and-handler &quot;/&quot; &quot;/tmp/coleslaw/index.html&quot;)
              (hunchentoot:create-folder-dispatcher-and-handler &quot;/&quot; &quot;/tmp/coleslaw/&quot;))))

(defmethod clim:disown-frame :before (fm (self durk))
  (format *debug-io* &quot;Cleaning up.~%&quot;)
  (when (hunchentoot:started-p (acceptor self))
    (hunchentoot:stop (acceptor self))))

(define-durk-command (com-start-acceptor)
    ((self 'hunchentoot:acceptor :gesture :select))
  (format *debug-io* &quot;Starting acceptor.~%&quot;)
  (hunchentoot:start self))

(define-durk-command (com-stop-acceptor)
    ((self 'hunchentoot:acceptor :gesture :select))
  (format *debug-io* &quot;Stopping acceptor.~%&quot;)
  (hunchentoot:stop self))
</code></pre>

<p>Here's the key part: instead of defining single method for presenting the frame,
we define a <code>:before</code> method that presents named commands and the acceptor:</p>

<pre><code>(clim:define-presentation-method clim:present :before ((self durk) (type durk) stream view &amp;rest args)
  (declare (ignore args))
  (clim:formatting-item-list (stream)
    (clim:map-over-command-table-names
     (lambda (name command)
       (declare (ignore name))
       (clim:formatting-cell (stream)
         (clim:surrounding-output-with-border (stream)
          (clim:present command 'clim:command :stream stream))))
     (clim:find-command-table 'durk)))
  (terpri stream)
  (present* (acceptor self) stream)
  (terpri stream))

(clim:define-presentation-method clim:present
    ((self hunchentoot:acceptor) (type hunchentoot:acceptor) stream view &amp;rest args)
  (declare (ignore view args))
  (clim:with-drawing-options (stream :ink (if (hunchentoot:started-p self)
                                              clim:+dark-green+
                                              clim:+dark-red+))
    (format stream &quot;~a~%&quot; self)))
</code></pre>

<p>In this section I mentioned the following topics:</p>

<ul>
<li>presentation methods may have auxiliary methods like <code>:after</code></li>
<li>we may extend existing applications by tweaking presentation methods and view</li>
<li>it is possible to enable and disable commands depending on the frame state</li>
<li>the frame life cycle starts when it is adopted, and ends when it is disowned</li>
<li>we may mix formatting macros, drawing options and stream output freely</li>
</ul>

<p>And voila, now we can preview the blog:</p>

<p><video controls>
  <source src="/static/video/coleslaw/coleslaw-gui.webm" type="video/webm">
</video></p>

<p><a id="orgbcf2313"></a></p>

<h1>Closing thoughts</h1>

<p>In this post we've covered many CLIM features that are useful for writing
applications. Some takeaways are:</p>

<ul>
<li>commands have a straightforward interpretation compatible with CLI</li>
<li>command tables encapsulate commands and may inherit from each other</li>
<li>frames encapsulate the dynamic context and organize windows</li>
<li>presentations allow for associating a presentation type with an object</li>
<li>presentation types may be used to specialize numerous presentation methods</li>
<li>views provide an easy way to customize the interface depending on context</li>
<li>presentation translators may be used to coerce object to the input context</li>
<li>presentation actions allow for triggering immediate handlers</li>
<li>commands may be conditionally disabled</li>
<li>the display function may be extended by specializing the function <code>present</code></li>
</ul>

<p>Adding an ad-hoc GUI to existing libraries amounts for not so many lines of code
and is moderately easy task. You may find the source code of this tutorial here:</p>

<p><a href="/static/misc/coleslaw-gui.lisp" >/static/misc/coleslaw-gui.lisp</a></p>

<p>While the tool is rather on the simplistic side, I'm already using it to preview
and manage a few of my blogs. Some extensions are due, but they'd rather make
the tutorial more complex - contrary to the intention of this post.</p>

<p>Happy hacking,<br/>
Daniel</p>
 ]]></description> </item><item> <title>Proxy Generic Function</title> <link>/posts/Proxy-Generic-Function.html</link> <pubDate>2023-10-03</pubDate> <author>Daniel Kochmański</author> <guid isPermaLink="true">/posts/Proxy-Generic-Function.html</guid> <category><![CDATA[ lisp ]]></category><category><![CDATA[ metaobject protocol ]]></category> <description><![CDATA[ <p>It is often hard to refactor software implementing an independent specification.
There are already clients of the API so we can't remove operators, and newly
added operators must play by the specified rules. There are a few possibilities:
break the user contract and make pre-existing software obsolete, or abandon some
improvements. There is also an option that software is written in Common Lisp,
so you can eat your cake and have it too.</p>

<p>CLIM has two protocols that have a big overlap: sheets and output records. Both
abstractions are organized in a similar way and have equivalent operators. In
this example let's consider a part of the protocol for managing hierarchies:</p>

<pre><code>;; Sheet hierarchy (sub-)protocol with an example implementation.
(defclass sheet () ()) ; protocol class
(defclass example-sheet (sheet)
  ((children :initform '() :accessor sheet-children)))

(defgeneric note-sheet-adopted (sheet)
  (:method (sheet) nil))

(defgeneric note-sheet-disowned (sheet)
  (:method (sheet) nil))

(defgeneric adopt-sheet (parent child)
  (:method ((parent example-sheet) child)
    (push child (sheet-children parent))
    (note-sheet-adopted child)))

(defgeneric disown-sheet (parent child &amp;optional errorp)
  (:method ((parent example-sheet) child &amp;optional (errorp t))
    (and errorp (assert (member child (sheet-children parent))))
    (setf (sheet-children parent)
          (remove child (sheet-children parent)))
    (note-sheet-disowned child)))

;; Output record hierarchy (sub-)protocol with an example implementation.
(defclass output-record () ()) ; protocol class
(defclass example-record (output-record)
  ((children :initform '() :accessor output-record-children)))

(defgeneric add-output-record (child parent)
  (:method (child (parent example-record))
    (push child (output-record-children parent))))

(defgeneric delete-output-record (child parent &amp;optional errorp)
  (:method (child (parent example-record) &amp;optional (errorp t))
    (and errorp (assert (member child (sheet-children parent))))
    (setf (output-record-children parent)
          (remove child (output-record-children parent)))))
</code></pre>

<p>Both protocols are very similar and do roughly the same thing. We are tempted to
flesh out a single protocol to reduce the cognitive overhead when dealing with
hierarchies.</p>

<pre><code>;; The mixin is not strictly necessary - output records and sheets may have
;; wildly different internal structures - this is for the sake of simplicity;
;; most notably it is _not_ a protocol class. We don't do protocol classes.
(defclass node-mixin ()
  ((scions :initform '() :accessor node-scions)))

(defgeneric note-node-parent-changed (node parent adopted-p)
  (:method (node parent adopted-p)
    (declare (ignore node parent adopted-p))
    nil))

(defgeneric insert-node (elder scion)
  (:method :after (elder scion)
    (note-node-parent-changed scion elder t))
  (:method ((elder node-mixin) scion)
    (push scion (node-scions elder))))

(defgeneric delete-node (elder scion)
  (:method :after (elder scion)
    (note-node-parent-changed scion elder nil))
  (:method ((elder node-mixin) scion)
    (setf (node-scions elder) (remove scion (node-scions elder)))))
</code></pre>

<p>We define a mixin class for simplicity. In principle we care only about the new
protocol and different classes may have different internal representations. Now
that we have a brand new unified protocol, it is time to rewrite the old code:</p>

<pre><code>;; Sheet hierarchy (sub-)protocol with an example implementation.
(defclass sheet () ()) ; protocol class
(defclass example-sheet (node-mixin sheet) ())

(defgeneric note-sheet-adopted (sheet)
  (:method (sheet)
    (declare (ignore sheet))
    nil))

(defgeneric note-sheet-disowned (sheet)
  (:method (sheet)
    (declare (ignore sheet))
    nil))

(defmethod note-node-parent-changed :after ((sheet sheet) parent adopted-p)
  (declare (ignore parent))
  (if adopted-p
      (note-sheet-adopted sheet)
      (note-sheet-disowned sheet)))

(defgeneric adopt-sheet (parent child)
  (:method (parent child)
    (insert-node parent child)))

(defgeneric disown-sheet (parent child &amp;optional errorp)
  (:method (parent child &amp;optional (errorp t))
    (and errorp (assert (member child (node-scions parent))))
    (delete-node parent child)))

;; Output record hierarchy (sub-)protocol with an example implementation.
(defclass output-record () ()) ; protocol class
(defclass example-record (node-mixin output-record) ())

(defgeneric add-output-record (child parent)
  (:method (child parent)
    (insert-node parent child)))

(defgeneric delete-output-record (child parent &amp;optional errorp)
  (:method (child parent &amp;optional (errorp t))
    (and errorp (assert (member child (node-scions parent))))
    (delete-node parent child)))
</code></pre>

<p>Peachy! Now we can call <code>(delete-node parent child)</code> and this will work equally
well for both sheets and output records. It is time to ship the code and boost
how clever we are (and advertise the new API). After a weekend we realize that
there is a problem with our solution!</p>

<p>Since the old API is alive and kicking, the user may still call <code>adopt-sheet</code>,
or if they want to switch to the new api they may call <code>insert-node</code>. This is
fine and we have rewritten all our code so that the new element will always be
added. But what about user methods?</p>

<p>There may be a legacy code that defines its additional constraints, for example:</p>

<pre><code>(defvar *temporary-freeze* nil)
(defmethod add-output-record :before (child (record output-record))
  (declare (ignore child record))
  (when *temporary-freeze*
    (error &quot;No-can-do's-ville, baby doll!&quot;)))
</code></pre>

<p>When the new code calls <code>insert-node</code>, then this method won't be called and the
constraint will fail. There is an interesting idea, that perhaps instead of
trampolining from the sheet protocol to the node protocol functions we could do
it the other way around: specialized node protocol methods will call the sheet
protocol functions. This is futile - the problem is symmetrical. In that case if
some legacy code calls <code>adopt-sheet</code>, then our node methods won't be called.</p>

<p>That's quite a pickle we are in. The main problem is that we are not in control
of all definitions and the cat is out of the bag. So what about the cake? The
cake is a lie of course! &#x2026; I'm kidding, of course there is the cake.</p>

<p>When Common Lisp programmers encounter a problem that seems impossible to solve,
they usually think of one of three solutions: write a macro, write a dsl
compiler or use the metaobject protocol. Usually the solution is a mix of these
three things. We are dealing with generic functions - the MOP it is.</p>

<p>The problem could be summarized as follows:</p>

<ol>
<li>We have under our control a new function that implements the program logic</li>
<li>We have under our control old functions that call the new function</li>
<li>We have legacy methods outside of our control defined on old functions</li>
<li>We will have new methods outside of our control defined on the new function</li>
<li>Sometimes lambda lists between protocols are not compatible</li>
</ol>

<p>We want the new function to call legacy methods when invoked, and we want to
ensure that old functions always call the new function (i.e it is not possible
for legacy <code>(sheet-disown-child :around)</code> methods to bypass <code>delete-node</code>).</p>

<p>In order to do that, we will define a new generic function class responsible for
mangling arguments when the method is called with <code>make-method-lambda</code>, and
proxying <code>add-method</code> to the target class. That's all. When a new legacy method
is added to the generic function <code>sheet-disown-child</code>, then it will be hijacked
and added to the generic function <code>delete-node</code> instead.</p>

<p>First some syntactic sugar. <code>defgeneric</code> is a good operator except that it does
error when we pass options that are not specified. Moreover some compilers are
tempted to macroexpand methods at compile time, so we'll expand the new macro in
the dynamic environment of a definition:</p>

<pre><code>(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun mappend (fun &amp;rest lists)
    (loop for results in (apply #'mapcar fun lists) append results)))

;;; syntactic sugar -- like defgeneric but accepts unknown options
(defmacro define-generic (name lambda-list &amp;rest options)
  (let ((declarations '())
        (methods '()))
    (labels ((parse-option (option)
               (destructuring-bind (name . value) option
                 (case name
                   (cl:declare
                    (setf declarations (append declarations value))
                    nil)
                   (:method
                     (push value methods)
                     nil)
                   ((:documentation :generic-function-class :method-class)
                    `(,name (quote ,@value)))
                   ((:argument-precedence-order :method-combination)
                    `(,name (quote ,value)))
                   (otherwise
                    `(,name (quote ,value))))))
             (expand-generic (options)
               `(c2mop:ensure-generic-function
                 ',name
                 :name ',name :lambda-list ',lambda-list
                 :declarations ',declarations ,@options))
             (expand-method (method)
               `(c2mop:ensure-method (function ,name) '(lambda ,@method))))
      ;; We always expand to ENSURE-FOO because we want dynamic variables like
      ;; *INSIDE-DEFINE-PROXY-P* to be correctly bound during the creation..
      `(progn
         ,(expand-generic (mappend #'parse-option options))
         ,@(mapcar #'expand-method methods)))))
</code></pre>

<p>Now we will add a macro that defines a proxy generic function. We include a
dynamic flag that will communicte to <code>make-method-lambda</code> and <code>add-method</code>
function, that we are still in the initialization phase and methods should be
added to the proxy generic function:</p>

<pre><code>(defvar *inside-define-proxy-p* nil)

(defmacro define-proxy-gf (name lambda-list &amp;rest options)
  `(let ((*inside-define-proxy-p* t))
     (define-generic ,name ,lambda-list
       (:generic-function-class proxy-generic-function)
       ,@options)))
</code></pre>

<p>The proxy generic function may have a different lambda list than the target.
That's indeed the case with our protocol - we don't have the argument <code>errorp</code>
in the function <code>delete-node</code>. We want to allow default methods in order to
implement that missing behavior. We will mangle arguments according to the
specified template in <code>:mangle-args</code> in the function <code>mangle-args-expressoin</code>.</p>

<pre><code>(defclass proxy-generic-function (c2mop:standard-generic-function)
  ((target-gfun                       :reader target-gfun)
   (target-args :initarg :target-args :reader target-args)
   (mangle-args :initarg :mangle-args :reader mangle-args))
  (:metaclass c2mop:funcallable-standard-class)
  (:default-initargs :target-gfun (error &quot;~s required&quot; :target-gfun)
                     :target-args nil
                     :mangle-args nil))

(defmethod shared-initialize :after ((gf proxy-generic-function) slot-names
                                     &amp;key (target-gfun nil target-gfun-p))
  (when target-gfun-p
    (assert (null (rest target-gfun)))
    (setf (slot-value gf 'target-gfun)
          (ensure-generic-function (first target-gfun)))))
</code></pre>

<p>To ensure that a proxied method can invoke <code>call-next-method</code> we must be able to
mangle arguments both ways. The target generic functions lambda list is stated
verbatim in <code>:target-args</code> argument, while the source generic function lambda
list is read from <code>c2mop:generic-function-lambda-list</code>.</p>

<p>The function <code>make-method-lambda</code> is tricky to get it right, but it gives quite
a bit of control over the method invocation. Default methods are added normally
so we don't mangle arguments in the trampoline method, otherwise we convert the
target call into the lambda list of a defined method:</p>

<pre><code>;;; MAKE-METHOD-LAMBDA is expected to return a lambda expression compatible with
;;; CALL-METHOD invocations in the method combination. The first argument are
;;; the prototype generic function arguments (the function a method is initially
;;; defined for) and the reminder are all arguments passed to CALL-METHOD - in a
;;; default combination there is one such argument - next-methods. The second
;;; returned value are extra initialization arguments for the method instance.
;;; 
;;; Our goal is to construct a lambda expression that will construct a function
;;; which instead of the prototype argument list accepts the proxied function
;;; arguments and mangles them to call the defined method body. Something like:
;;;
#+ (or)
(lambda (proxy-gfun-call-args &amp;rest call-method-args)
  (flet ((original-method (method-arg-1 method-arg-2 ...)))
    (apply #'original-method (mangle-args proxy-gfun-call-args))))

(defun mangle-args-expression (gf type args)
  (let ((lambda-list (ecase type
                       (:target (target-args gf))
                       (:source (c2mop:generic-function-lambda-list gf)))))
    `(destructuring-bind ,lambda-list ,args
       (list ,@(mangle-args gf)))))

(defun mangle-method (gf gf-args lambda-expression)
  (let ((mfun (gensym)))
    `(lambda ,(second lambda-expression)
       (flet ((call-next-method (&amp;rest args)
                (if (null args)
                    (call-next-method)
                    ;; CALL-NEXT-METHOD is called with arguments are meant for
                    ;; the proxy function lambda list. We first need to destruct
                    ;; them and then mangle again.
                    (apply #'call-next-method 
                           ,(mangle-args-expression gf :target
                             (mangle-args-expression gf :source 'args))))))
         (flet ((,mfun ,@(rest lambda-expression)))
           (apply (function ,mfun) ,(mangle-args-expression gf :target gf-args)))))))

(defmethod c2mop:make-method-lambda
    ((gf proxy-generic-function) method lambda-expression environment)
  (declare (ignorable method lambda-expression environment))
  (if (or *inside-define-proxy-p* (null (mangle-args gf)))
      (call-next-method)
      `(lambda (proxy-args &amp;rest call-method-args)
         (apply ,(call-next-method gf method (mangle-method gf 'proxy-args lambda-expression) environment)
                proxy-args call-method-args))))
</code></pre>

<p>That leaves us with the last method <code>add-method</code> that decides where to add the
method - to the proxy function or to the target function.</p>

<pre><code>(defmethod add-method ((gf proxy-generic-function) method)
  (when *inside-define-proxy-p*
    (return-from add-method (call-next-method)))
  ;; The warning will go away in the production code because we don't want to
  ;; barf at a normal client code.
  (warn &quot;~s is deprecated, please use ~s instead.&quot;
        (c2mop:generic-function-name gf)
        (c2mop:generic-function-name (target-gfun gf)))
  (if (or (typep method 'c2mop:standard-accessor-method) (null (mangle-args gf)))
      ;; XXX readers and writers always have congruent lambda lists so this should
      ;; be fine. Besides we don't know how to construct working accessors on some
      ;; (ekhm sbcl) implementations, because they have problems with invoking
      ;; user-constructed standard accessors (with passed :SLOT-DEFINITION SLOTD).
      (add-method (target-gfun gf) method)
      (let* ((method-class (class-of method))
             (old-lambda-list (c2mop:generic-function-lambda-list gf))
             (new-lambda-list (target-args gf))
             (new-specializers (loop with spec = (c2mop:method-specializers method)
                                     for arg in new-lambda-list
                                     until (member arg '(&amp;rest &amp;optional &amp;key))
                                     collect (nth (position arg old-lambda-list) spec)))
             ;; It would be nice if we could reinitialize the method.. but we can't.
             (new-method (make-instance method-class
                                        :lambda-list new-lambda-list
                                        :specializers new-specializers
                                        :qualifiers (method-qualifiers method)
                                        :function (c2mop:method-function method))))
        (add-method (target-gfun gf) new-method))))
</code></pre>

<p>That's it. We've defined a new generic function class that allows specifying
proxies. Now we can replace definitions of generic functions that are under our
control. The new (the final) implementation looks like this:</p>

<pre><code>;; Sheet hierarchy (sub-)protocol with an example implementation.
(defclass sheet () ()) ; protocol class
(defclass example-sheet (node-mixin sheet) ())

(defgeneric note-sheet-adopted (sheet)
  (:method (sheet)
    (declare (ignore sheet))
    nil))

(defgeneric note-sheet-disowned (sheet)
  (:method (sheet)
    (declare (ignore sheet))
    nil))

(defmethod note-node-parent-changed :after ((sheet sheet) parent adopted-p)
  (declare (ignore parent))
  (if adopted-p
      (note-sheet-adopted sheet)
      (note-sheet-disowned sheet)))

(define-proxy-gf adopt-sheet (parent child)
  (:target-gfun insert-node)
  (:target-args parent child)
  (:mangle-args parent child)
  (:method (parent child)
    (insert-node parent child)))

(define-proxy-gf disown-sheet (parent child &amp;optional errorp)
  (:target-gfun delete-node)
  (:target-args parent child)
  (:mangle-args parent child nil)
  (:method (parent child &amp;optional (errorp t))
    (and errorp (assert (member child (node-scions parent))))
    (delete-node parent child)))

;; Output record hierarchy (sub-)protocol with an example implementation.
(defclass output-record () ()) ; protocol class
(defclass example-record (node-mixin output-record) ())

(define-proxy-gf add-output-record (child parent)
  (:target-gfun insert-node)
  (:target-args parent child)
  (:mangle-args child parent)
  (:method (child parent)
    (insert-node parent child)))

(define-proxy-gf delete-output-record (child parent &amp;optional errorp)
  (:target-gfun insert-node)
  (:target-args parent child)
  (:mangle-args child parent)
  (:method (child parent &amp;optional (errorp t))
    (and errorp (assert (member child (node-scions parent))))
    (delete-node parent child)))
</code></pre>

<p>And this code is defined in a separate compilation unit:</p>

<pre><code>;; Legacy code in a third-party library.
(defvar *temporary-freeze* nil)
(defmethod add-output-record :before (child (record output-record))
  (declare (ignore child))
  (when *temporary-freeze*
    (error &quot;No-can-do's-ville, baby doll!&quot;)))

;; Bleeding edge code in an experimental third-party library.
(defvar *logging* nil)
(defmethod insert-node :after ((record output-record) child)
  (declare (ignore child))
  (when *logging*
    (warn &quot;The record ~s has been extended!&quot; record)))
</code></pre>

<p>Dare we try it? You bet we do!</p>

<pre><code>(defparameter *parent* (make-instance 'example-record))
(defparameter *child1* (make-instance 'example-record))
(defparameter *child2* (make-instance 'example-record))
(defparameter *child3* (make-instance 'example-record))
(defparameter *child4* (make-instance 'example-record))
(defparameter *child5* (make-instance 'example-record))

(add-output-record *child1* *parent*)
(print (node-scions *parent*))        ;1 element

(insert-node *parent* *child2*)
(print (node-scions *parent*))        ;1 element

;; So far good!
(let ((*temporary-freeze* t))
  (handler-case (adopt-sheet *parent* *child3*)
    (error     (c) (print `(&quot;Good!&quot; ,c)))
    (:no-error (c) (print `(&quot;Bad!!&quot; ,c))))

  (handler-case (add-output-record *child3* *parent*)
    (error     (c) (print `(&quot;Good!&quot; ,c)))
    (:no-error (c) (print `(&quot;Bad!!&quot; ,c))))

  (handler-case (insert-node *parent* *child3*)
    (error     (c) (print `(&quot;Good!&quot; ,c)))
    (:no-error (c) (print `(&quot;Bad!!&quot; ,c)))))

;; Still perfect!
(let ((*logging* t))
  (handler-case (adopt-sheet *parent* *child3*)
    (error     (c) (print `(&quot;Bad!&quot; ,c)))
    (warning   (c) (print `(&quot;Good!&quot;,c))))

  (handler-case (add-output-record *child4* *parent*)
    (error     (c) (print `(&quot;Bad!&quot; ,c)))
    (warning   (c) (print `(&quot;Good!&quot;,c))))

  (handler-case (insert-node *parent* *child5*)
    (error     (c) (print `(&quot;Bad!&quot; ,c)))
    (warning   (c) (print `(&quot;Good!&quot;,c)))))

(print `(&quot;We should have 5 children -- &quot; ,(length (node-scions *parent*))))
(print (node-scions *parent*))
</code></pre>

<p>This solution has one possible drawback. We add methods from the proxy generic
function to the target generic function without discriminating. That means that
applicable methods defined on <code>adopt-sheet</code> are called when <code>add-output-record</code>
is invoked (and vice versa). Moreover methods with the same set of specializers
in the target function may replace each other. On the flip side this is what we
arguably want &#x2013; the unified protocol exhibits full behavior of all members. We
could have mitigated this problem by signaling an error for conflicting methods
from different proxies, but if you think about it, a conforming program must not
define methods that are not specialized on a subclass of the standard class -
otherwise they risk overwriting internal methods! In other words all is good.</p>

<p><strong>Edit 1</strong> Another caveat is that methods for the proxy generic function must be
defined in a different compilation unit than the function. This is because of
limitations of <code>defmethod</code> - the macro calls <code>make-method-lambda</code> when it is
expanding the body (at compile time), while the function definition is processed
at the execution time.</p>

<p>That means that <code>make-method-lambda</code> during the first compilation will be called
with a <code>standard-generic-function</code> prototype and the proxy won't work.</p>

<p>Cheers!<br/>
Daniel</p>

<p>P.S. if you like writing like this you may consider supporting me on <a href="https://www.patreon.com/jackdaniel_kochmanski" >Patreon</a>.</p>
 ]]></description> </item><item> <title>Method Combinations</title> <link>/posts/Method-Combinations.html</link> <pubDate>2023-01-18</pubDate> <author>Daniel Kochmański</author> <guid isPermaLink="true">/posts/Method-Combinations.html</guid> <category><![CDATA[ lisp ]]></category><category><![CDATA[ method combinations ]]></category> <description><![CDATA[ <h1>Table of Contents</h1>

<ol>
<li><a href="#org9283781" >Introduction</a></li>
<li><a href="#org3207420" >Defining method combinations - the short form</a></li>
<li><a href="#orgeff6ad1" >Defining method combinations - the long form</a>

<ol>
<li><a href="#org3ad4474" >The Hooker</a></li>
<li><a href="#org72974df" >The Memoizer</a></li>
</ol></li>
<li><a href="#orge9de1fa" >Conclusions</a></li>
</ol>

<p><a id="org9283781"></a></p>

<p><em>Update [2023-01-23]</em></p>

<p>Christophe Rhodes pointed out that &quot;The Hooker&quot; method combination is
not conforming because there are multiple methods with the same &quot;role&quot;
that can't be ordered and that have different qualifiers:</p>

<blockquote>
<p>Note that two methods with identical specializers, but with different qualifiers, are not ordered by the algorithm described in Step 2 of the method selection and combination process described in Section 7.6.6 (Method Selection and Combination). Normally the two methods play different roles in the effective method because they have different qualifiers, and no matter how they are ordered in the result of Step 2, the effective method is the same. If the two methods play the same role and their order matters, an error is signaled. This happens as part of the qualifier pattern matching in define-method-combination.</p>
</blockquote>

<p>http://www.lispworks.com/documentation/HyperSpec/Body/m_defi_4.htm</p>

<p>So instead of using qualifier patterns we should use qualifier
predicates. They are not a subject of the above paragraph because of
its last sentence (there is also an example in the spec that has
multiple methods with a predicate). So instead of</p>

<pre><code>(define-method-combination hooker ()
  (... (hook-before (:before*)) ...) ...)
</code></pre>

<p>the method combination should use:</p>

<pre><code>(defun hook-before-p (method-qualifier)
  (typep method-qualifier '(cons (eql :before) (cons t null))))

(define-method-combination hooker ()
  (... (hook-before hook-before-p) ...) ...)
</code></pre>

<p>and other &quot;hook&quot; groups should also use predicates.</p>

<p>Another thing worth mentioning is that both ECL and SBCL addressed
issues with the qualifier pattern matching and :arguments since the
publication of this blog post.</p>

<h1>Introduction</h1>

<p>Method combinations are used to compute the effective method for a generic
function. An effective method is a body of the generic function that combines
a set of applicable methods computed based on the invocation arguments.</p>

<p>For example we may have a function responsible for reporting the object
status and each method focuses on a different aspect of the object. In that
case we may want to append all results into a list:</p>

<pre><code>(defgeneric status (object)
  (:method-combination append))

(defclass base-car ()
  ((engine-status :initarg :engine :accessor engine-status)
   (wheels-status :initarg :wheels :accessor wheels-status)
   (fuel-level :initarg :fuel :accessor fuel-level))
  (:default-initargs :engine 'ok :wheels 'ok :fuel 'full))

(defmethod status append ((object base-car))
  (list :engine (engine-status object)
        :wheels (wheels-status object)
        :fuel (fuel-level object)))

(defclass premium-car (base-car)
  ((gps-status :initarg :gps :accessor gps-status)
   (nitro-level :initarg :nitro :accessor nitro-level))
  (:default-initargs :gps 'no-signal :nitro 'low))

(defmethod status append ((object premium-car))
  (list :gps (gps-status object)
        :nitro (nitro-level object)))

CL-USER&gt; (status (make-instance 'premium-car))
(:GPS NO-SIGNAL :NITRO LOW :ENGINE OK :WHEELS OK :FUEL FULL)

CL-USER&gt; (status (make-instance 'base-car))
(:ENGINE OK :WHEELS OK :FUEL FULL)
</code></pre>

<p>The effective method may look like this:</p>

<pre><code>(append (call-method #&lt;method status-for-premium-car&gt;)
        (call-method #&lt;method status-for-base-car&gt;   ))
</code></pre>

<p>Note that <code>append</code> is a function so all methods are called. It is possible to
use other operators (for example a macro <code>and</code>) and then the invocation of
particular methods may be conditional:</p>

<pre><code>(and (call-method #&lt;method can-repair-p-for-premium-car&gt;)
     (call-method #&lt;method can-repair-p-for-base-car&gt;   ))
</code></pre>

<p><a id="org3207420"></a></p>

<h1>Defining method combinations - the short form</h1>

<p>The short form allows us to define a method combination in the spirit of
the previous example:</p>

<pre><code>(OPERATOR (call-method #&lt;m1&gt;)
          (call-method #&lt;m2&gt;)
          ...)
</code></pre>

<p>For example we may want to return as the second value the count of odd
numbers:</p>

<pre><code>(defun sum-and-count-odd (&amp;rest args)
  (values (reduce #'+ args)
          (count-if #'oddp args)))

(define-method-combination sum-and-count-odd)

(defclass a () ())
(defclass b (a) ())
(defclass c (b) ())

(defgeneric num (o)
  (:method-combination sum-and-count-odd)
  (:method sum-and-count-odd ((o a)) 1)
  (:method sum-and-count-odd ((o b)) 2)
  (:method sum-and-count-odd ((o c)) 3)
  (:method :around ((o c))
    (print &quot;haa!&quot;)
    (call-next-method)))

(num (make-instance 'b)) ;; (values 3 1)
(num (make-instance 'c)) ;; (values 6 2)
</code></pre>

<p>Note that the short form supports also around methods. It is also important to
note that effective methods are cached, that is unless the generic function or
the method combination changes, the computation of the effective method may be
called only once per the set of effective methods.</p>

<p>Admittedly these examples are not very useful. Usually we operate on data
stored in instances and this is not a good abstraction to achieve that. Method
combinations are useful to control method invocations and their results. Here
is another example:</p>

<pre><code>(defmacro majority-vote (&amp;rest method-calls)
  (let* ((num-methods (length method-calls))
         (tie-methods (/ num-methods 2)))
    `(prog ((yes 0) (no 0))
        ,@(loop for invocation in method-calls
                append `((if ,invocation
                             (incf yes)
                             (incf no))
                         (cond
                           ((&gt; yes ,tie-methods)
                            (return (values t yes no)))
                           ((&gt; no ,tie-methods)
                            (return (values nil yes no))))))
        (error &quot;we have a tie! ~d ~d&quot; yes no))))

(define-method-combination majority-vote)

(defclass a () ())
(defclass b (a) ())
(defclass c (b) ())
(defclass d (c) ())

(defgeneric foo (object param)
  (:method-combination majority-vote)
  (:method majority-vote ((o a) param) nil)
  (:method majority-vote ((o b) param) t)
  (:method majority-vote ((o c) param) t)
  (:method majority-vote ((o d) param) nil))

(foo (make-instance 'a) :whatever) ; (values nil 0 1)
(foo (make-instance 'b) :whatever) ; #&lt;error tie 1 1&gt;
(foo (make-instance 'c) :whatever) ; (values t 2 0)
(foo (make-instance 'd) :whatever) ; #&lt;error tie 2 2&gt;
</code></pre>

<p><a id="orgeff6ad1"></a></p>

<h1>Defining method combinations - the long form</h1>

<p>The long form is much more interesting. It allows us to specify numerous
qualifiers and handle methods without any qualifiers at all.</p>

<p><a id="org3ad4474"></a></p>

<h2>The Hooker</h2>

<p>Here we will define a method combination that allows us to define named hooks
that are invoked before or after the method. It is possible to have any number
of hooks for the same set of arguments (something we can't achieve with the
standard <code>:before</code> and <code>:after</code> auxiliary methods):</p>

<pre><code>(defun combine-auxiliary-methods (primary around before after)
  (labels ((call-primary ()
             `(call-method ,(first primary) ,(rest primary)))
           (call-methods (methods)
             (mapcar (lambda (method)
                       `(call-method ,method))
                     methods))
           (wrap-after (the-form)
             (if after
                 `(multiple-value-prog1 ,the-form
                    ,@(call-methods after))
                 the-form))
           (wrap-before (the-form)
             (if before
                 `(progn
                    ,@(call-methods before)
                    ,the-form)
                 the-form))
           (wrap-around (the-form)
             (if around
                 `(call-method ,(first around)
                               (,@(rest around)
                                (make-method ,the-form)))
                 the-form)))
    (wrap-around (wrap-after (wrap-before (call-primary))))))

(define-method-combination hooker ()
  ((normal-before (:before))
   (normal-after  (:after)
                  :order :most-specific-last)
   (normal-around (:around))
   (hook-before   (:before *))
   (hook-after    (:after  *)
                  :order :most-specific-last)
   (hook-around   (:around *))
   (primary () :required t))
  (let ((around (append hook-around normal-around))
        (before (append hook-before normal-before))
        (after  (append normal-after hook-after)))
    (combine-auxiliary-methods primary around before after)))
</code></pre>

<p>With this we may define a generic function and associated methods similar to
other functions with an extra feature - we may provide named <code>:before</code>,
<code>:after</code> and <code>:around</code> methods. Named auxiliary methods take a precedence over
unnamed ones. Only after that the specialization is considered. There is one
caveat - <code>PCL</code>-derived <code>CLOS</code> implementations (<code>clasp</code>, <code>cmucl</code>, <code>ecl</code>,
<code>sbcl</code>) currently (<span class="timestamp-wrapper"><span class="timestamp">[2023-01-18 śro]</span></span>) have a bug preventing wildcard qualifier
pattern symbol <code>*</code> from working. So better download <code>ccl</code> or wait for
fixes. Here's an example for using it:</p>

<pre><code>;;; The protocol.
(defgeneric note-buffer-dimensions-changed (buffer w h)
  (:method (b w h)
    (declare (ignore b w h))
    nil))

(defgeneric change-dimensions (buffer w h)
  (:method-combination hooker))

;;; The implementation of unspecialized methods.
(defmethod change-dimensions :after (buffer w h)
  (note-buffer-dimensions-changed buffer w h))

;;; The stanard class.
(defclass buffer ()
  ((w :initform 0 :accessor w)
   (h :initform 0 :accessor h)))

;;; The implementation for the standard class.
(defmethod change-dimensions ((buffer buffer) w h)
  (print &quot;... Changing the buffer size ...&quot;)
  (setf (values (w buffer) (h buffer))
        (values w h)))

(defmethod note-buffer-dimensions-changed ((buffer buffer) w h)
  (declare (ignore buffer w h))
  (print &quot;... Resizing the viewport ...&quot;))

;;; Some dubious-quality third-party code that doesn't want to interfere with
;;; methods defined by the implementation.
(defmethod change-dimensions :after system (buffer w h)
  (print `(log :something-changed ,buffer ,w ,h)))

(defmethod change-dimensions :after my-hook ((buffer buffer) w h)
  (print `(send-email! :me ,buffer ,w ,h)))

CL-USER&gt; (defvar *buffer* (make-instance 'buffer))
*BUFFER*
CL-USER&gt; (change-dimensions *buffer* 10 30)

&quot;... Changing the buffer size ...&quot; 
&quot;... Resizing the viewport ...&quot; 
(LOG :SOMETHING-CHANGED #&lt;BUFFER #x30200088220D&gt; 10 30) 
(SEND-EMAIL! :ME #&lt;BUFFER #x30200088220D&gt; 10 30) 
10
30
</code></pre>

<p><a id="org72974df"></a></p>

<h2>The Memoizer</h2>

<p>Another example (this time it will work on all implementations) is optional
memoization of the function invocation. If we define a method with the
qualifier <code>:memoize</code> then the result will be cached depending on arguments.
The method combination allows also &quot;normal&quot; auxiliary functions by reusing the
function <code>combine-auxiliary-methods</code> from the previous section.</p>

<p>The function <code>ensure-memoized-result</code> accepts the following arguments:</p>

<ul>
<li><strong><code>test</code>:</strong> compare generations</li>
<li><strong><code>memo</code>:</strong> a form that returns the current generation</li>
<li><strong><code>cache-key</code>:</strong> a list composed of a generic function and its arguments</li>
<li><strong><code>form</code>:</strong> a form implementing the method to be called</li>
</ul>

<p>When the current generation is <code>nil</code> that means that caching is disabled and
we remove the result from the cache. Otherwise we use the <code>test</code> to compare
the generation of a cached value and the current one - if they are the same,
then the cached value is returned. Otherwise it is returned.</p>

<pre><code>(defparameter *memo* (make-hash-table :test #'equal))
(defun ensure-memoized-result (test memo cache-key form)
  `(let ((new-generation ,memo))
     (if (null new-generation)
         (progn
           (remhash ,cache-key *memo*)
           ,form)
         (destructuring-bind (old-generation . cached-result)
             (gethash ,cache-key *memo* '(nil))
           (apply #'values
                  (if (,test old-generation new-generation)
                      cached-result
                      (rest
                       (setf (gethash ,cache-key *memo*)
                             (list* new-generation (multiple-value-list ,form))))))))))
</code></pre>

<p>The method with the qualifier <code>:memoize</code> is used to compute the current
generation key. When there is no such method then the function behaves as if
the standard method combination is used. The method combination accepts a
single argument test, so it is possible to define different predicates for
deciding whether the cache is up-to-date or not.</p>

<pre><code>(define-method-combination memoizer (test)
  ((before (:before))
   (after  (:after) :order :most-specific-last)
   (around (:around))
   (memoize (:memoize))
   (primary () :required t))
  (:arguments &amp;whole args)
  (:generic-function function)
  (let ((form (combine-auxiliary-methods primary around before after))
        (memo `(call-method ,(first memoize) ,(rest memoize)))
        (ckey `(list* ,function ,args)))
    (if memoize
        (ensure-memoized-result test memo ckey form)
        form)))
</code></pre>

<p>Now let's define a function with &quot;our&quot; method combination. We will use a
counter to verify that values are indeed cached.</p>

<pre><code>(defparameter *counter* 0)

(defgeneric test-function (arg &amp;optional opt)
  (:method-combination memoizer eql))

(defmethod test-function ((arg integer) &amp;optional opt)
  (list* `(:counter ,(incf *counter*)) arg opt))

CL-USER&gt; (test-function 42)
((:COUNTER 1) 42)
CL-USER&gt; (test-function 42)
((:COUNTER 2) 42)
CL-USER&gt; (defmethod test-function :memoize ((arg integer) &amp;optional (cache t))
           (and cache :gen-z))
#&lt;STANDARD-METHOD TEST-FUNCTION :MEMOIZE (INTEGER)&gt;
CL-USER&gt; (test-function 42)
((:COUNTER 3) 42)
CL-USER&gt; (test-function 42)
((:COUNTER 3) 42)
CL-USER&gt; (test-function 42 nil)
((:COUNTER 4) 42)
CL-USER&gt; (test-function 42)
((:COUNTER 3) 42)
CL-USER&gt; (test-function 43)
((:COUNTER 5) 43)
CL-USER&gt; (test-function 43)
((:COUNTER 5) 43)
CL-USER&gt; (defmethod test-function :memoize ((arg (eql 43)) &amp;optional (cache t))
           (and cache :gen-x))
#&lt;STANDARD-METHOD TEST-FUNCTION :MEMOIZE ((EQL 43))&gt;
CL-USER&gt; (test-function 43)
((:COUNTER 6) 43)
CL-USER&gt; (test-function 43)
((:COUNTER 6) 43)
CL-USER&gt; (test-function 42)
((:COUNTER 3) 42)
</code></pre>

<p><a id="orge9de1fa"></a></p>

<h1>Conclusions</h1>

<p>Method combinations are a feature that is often overlooked but give a great
deal of control over the generic function invocation. The fact that <code>ccl</code> is
the only implementation from a few that I've tried which got method
combinations &quot;right&quot; doesn't surprise me - I've always had an impression that
it shines in many unexpected places.</p>
 ]]></description> </item><item> <title>Buffering Output</title> <link>/posts/Buffering-Output.html</link> <pubDate>2022-10-01</pubDate> <author>Daniel Kochmański</author> <guid isPermaLink="true">/posts/Buffering-Output.html</guid> <category><![CDATA[ lisp ]]></category><category><![CDATA[ output ]]></category><category><![CDATA[ buffering ]]></category> <description><![CDATA[ <h3>Single buffering</h3>

<p>In graphical applications buffering of output is necessary to avoid
flickering - a displeasing effect where mid-drawing artifacts are displayed on
the screen. For example consider the following function:</p>

<pre><code>(defun draw-scene (sheet)
  (draw-rectangle* sheet 125 125 175 175 :ink +red+)
  (draw-rectangle* sheet 125 125 175 175 :ink +blue+))
</code></pre>

<p>Here we draw two rectangles one on top of the other. If the red square is
visible for a brief period of time before the blue one, then it is called
flickering. To avoid this effect a concept of output buffering was invented -
only when the output is ready for display, show it on the screen.</p>

<h3>Double buffering</h3>

<p>With double buffering we draw on the &quot;back&quot; buffer, and when done the back
buffer contents are shown on the front buffer.</p>

<pre><code>(defun game-loop ()
  (loop (draw-scene sheet)
        (swap-buffers sheet (buffer-1 sheet) (buffer-2 sheet))))
</code></pre>

<h3>Triple buffering</h3>

<p>The triple buffering is used when new scenes are produced much faster than the
front buffer could be updated. We have &quot;render&quot;, &quot;ready&quot; and &quot;front&quot; buffers.
The implicit assumption is that the game loop and the display loop operate in
separate threads.</p>

<pre><code>(defun display-loop ()
  (loop (swap-buffers sheet (buffer-2 sheet) (buffer-3 sheet))
        (display-buffer sheet (buffer-3 sheet))))
</code></pre>

<h3>Incremental and non-incremental rendering</h3>

<p>If each frame is drawn from scratch (like in many games), then it doesn't
matter whether the &quot;swap&quot; operation copies or swaps buffers. Some applications
however treat the canvas incrementally. In this case losing the old content is
not acceptable and we must copy data.</p>

<pre><code><span class="code"><span class="comment">;;; The frame is rendered from scratch (not incremental)
</span><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> swap-buffers <span class="paren2">(<span class="code">sheet buffer-1 buffer-2</span>)</span>
  `<span class="paren2">(<span class="code"><i><span class="symbol">with-swap-lock</span></i> <span class="paren3">(<span class="code">sheet</span>)</span>
     <span class="paren3">(<span class="code">rotatef ,buffer-1 ,buffer-2</span>)</span></span>)</span></span>)</span>

<span class="comment">;;; The frame is rendered based on the previosu content (incremental)
</span><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> copy-buffers <span class="paren2">(<span class="code">sheet buffer-1 buffer-2</span>)</span>
  `<span class="paren2">(<span class="code"><i><span class="symbol">with-swap-lock</span></i> <span class="paren3">(<span class="code">sheet</span>)</span>
     <span class="paren3">(<span class="code">copy-array ,buffer-1 ,buffer-2</span>)</span></span>)</span></span>)</span></span></code></pre>

<p>Copying data is more expensive than rotating buffers. That said sometimes
re-rendering a frame from scratch may outweigh that cost. Incremental
rendering resembles drawing on a paper - unless we clear it manually, the old
content will be visible.</p>

<h3>Mixed buffering</h3>

<p>Sometimes we may want to draw directly on the front buffer. This is the most
performant when we write each pixel exactly once (for example when we render
an image). In this case we are not only expected to synchronize the front
buffer with the back buffer, but also the other way around.</p>

<pre><code><span class="code"><span class="comment">;;; Buffer-1 is "back", Buffer-2 is "front".
</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> activate-single-buffering <span class="paren2">(<span class="code"></span>)</span>
  <span class="comment">;; Update the front buffer immedietely.
</span>  <span class="paren2">(<span class="code">copy-buffers sheet <span class="paren3">(<span class="code">buffer-1 sheet</span>)</span> <span class="paren3">(<span class="code">buffer-2 sheet</span>)</span></span>)</span></span>)</span>

<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> activate-double-buffering <span class="paren2">(<span class="code"></span>)</span>
  <span class="comment">;; Synchronize the back buffer with the front-buffer.
</span>  <span class="paren2">(<span class="code">copy-buffers sheet <span class="paren3">(<span class="code">buffer-2 sheet</span>)</span> <span class="paren3">(<span class="code">buffer-1 sheet</span>)</span></span>)</span></span>)</span></span></code></pre>

<p>Otherwise, if we turn the double buffering back on, the back buffer won't
contain the data that was drawn when the output was single-buffered.</p>

<h3>Closing thoughts</h3>

<p>There are many techniques that makes this more performant. My main goal with
this post was to emphasize the difference between the incremental and
non-incremental rendering that was usually ommited in materials I've found on
the Internet.</p>

<p>Interesting reads:</p>

<ul>
<li><p><a href="https://wiki.osdev.org/Double_Buffering" >https://wiki.osdev.org/Double_Buffering</a></p></li>
<li><p><a href="https://emersion.fr/blog/2019/intro-to-damage-tracking/" >https://emersion.fr/blog/2019/intro-to-damage-tracking/</a></p></li>
</ul>
 ]]></description> </item> </channel> </rss>