<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>veer66</title>
    <link>https://qua.name/veer66/</link>
    <description>veer66</description>
    <pubDate>Thu, 16 Apr 2026 08:06:29 +0200</pubDate>
    <item>
      <title>Building an MCP Server with Common Lisp</title>
      <link>https://qua.name/veer66/building-an-mcp-server-with-common-lisp-l6jg</link>
      <description>&lt;![CDATA[I recently wanted to learn about MCP (Model Context Protocol). As someone whose default programming language is Common Lisp, I naturally decided to build an MCP server using Lisp.&#xA;&#xA;Thanks to the creators of 40ants-mcp, the library provides a nice pattern and code structure that I really like. However, I struggled significantly with installation and getting started. What should have taken minutes ended up taking days.&#xA;&#xA;I&#39;m sharing my experience here so that others who want to build MCP servers in Common Lisp can get started in minutes, not days like I did.&#xA;&#xA;Prerequisites&#xA;&#xA;Before you begin, make sure you have the following installed:&#xA;&#xA;SBCL - A high-performance Common Lisp compiler&#xA;Roswell - A Common Lisp implementation manager and script runner&#xA;Quicklisp - The de facto package manager for Common Lisp&#xA;Ultralisp - A community-driven distribution of Common Lisp libraries&#xA;&#xA;Installing Ultralisp&#xA;&#xA;In SBCL with Quicklisp, you can enable Ultralisp by:&#xA;&#xA;(ql-dist:install-dist &#34;http://dist.ultralisp.org/&#34; :prompt nil)&#xA;&#xA;(How to install SBCL and Quicklisp is in the appendix.)&#xA;&#xA;The Gotcha: Loading 40ants-mcp&#xA;&#xA;Here&#39;s the issue that cost me days: when you try to load 40ants-mcp with:&#xA;&#xA;(ql:quickload :40ants-mcp)&#xA;&#xA;You might encounter errors. The solution is simple but not obvious—load jsonrpc first:&#xA;&#xA;(ql:quickload :jsonrpc)&#xA;(ql:quickload :40ants-mcp)&#xA;&#xA;This dependency isn&#39;t automatically resolved, which was the source of my frustration.&#xA;&#xA;Creating Your MCP Server&#xA;&#xA;Here&#39;s a minimal example from my mcp-exper package:&#xA;&#xA;(in-package :mcp-exper)&#xA;&#xA;(openrpc-server:define-api (mi-tools :title &#34;mi-tools&#34;))&#xA;&#xA;(40ants-mcp/tools:define-tool (mi-tools add) (a b)&#xA;  (:summary &#34;just add&#34;)&#xA;  (:param a integer &#34;a&#34;)&#xA;  (:param b integer &#34;b&#34;)&#xA;  (:result text-content)&#xA;  (make-instance &#39;text-content :text (format nil &#34;~a&#34; (+ a b))))&#xA;&#xA;(defun start-server ()&#xA;  (40ants-mcp/server/definition:start-server mi-tools))&#xA;&#xA;Key points:&#xA;&#xA;Use openrpc-server:define-api to define your API&#xA;Use 40ants-mcp/tools:define-tool to define tools&#xA;Return text-content instances for text results (MCP requires specific content types)&#xA;&#xA;Running the Server&#xA;&#xA;Create a Roswell script (mi-mcp-server.ros):&#xA;&#xA;!/bin/sh&#xA;|-- mode:lisp --|&#xA;exec ros -Q -- $0 &#34;$@&#34;&#xA;|#&#xA;(progn&#xA;  (ros:ensure-asdf)&#xA;  #+quicklisp(ql:quickload &#39;(:mcp-exper) :silent t))&#xA;&#xA;(defun main (&amp;rest argv)&#xA;  (declare (ignorable argv))&#xA;  (mcp-exper:start-server))&#xA;&#xA;Quick Test&#xA;&#xA;Run directly with Roswell:&#xA;&#xA;ros mi-mcp-server.ros&#xA;&#xA;Production Installation&#xA;&#xA;Build and install as an executable:&#xA;&#xA;ros build mi-mcp-server.ros&#xA;install -m 0755 mi-mcp-server $HOME/.local/bin/&#xA;&#xA;Make sure $HOME/.local/bin is in your PATH.&#xA;&#xA;Integrating with Opencode&#xA;&#xA;To enable your MCP server in opencode, add this to ~/.config/opencode/opencode.json:&#xA;&#xA;{&#xA;    &#34;mcp&#34;: {&#xA;        &#34;mi-tools&#34;: {&#xA;            &#34;type&#34;: &#34;local&#34;,&#xA;            &#34;command&#34;: [&#34;mi-mcp-server&#34;],&#xA;            &#34;enabled&#34;: true&#xA;        }&#xA;    }&#xA;}&#xA;&#xA;Conclusion&#xA;&#xA;Building MCP servers with Common Lisp is straightforward once you know the tricks. The 40ants-mcp library is well-designed, and the OpenRPC integration works smoothly.&#xA;&#xA;I hope this guide saves you the days of frustration I experienced. Happy hacking!&#xA;&#xA;---&#xA;&#xA;The full source code for this example is available at mcp-exper.&#xA;&#xA;Appendix: Installing SBCL&#xA;&#xA;macOS&#xA;&#xA;brew install sbcl&#xA;&#xA;Debian/Ubuntu&#xA;&#xA;apt install sbcl&#xA;&#xA;Arch Linux&#xA;&#xA;pacman -S sbcl&#xA;&#xA;Appendix: Installing Quicklisp&#xA;&#xA;Download and install Quicklisp:&#xA;&#xA;wget https://beta.quicklisp.org/quicklisp.lisp&#xA;sbcl --load quicklisp.lisp \&#xA;        --eval &#39;(quicklisp-quickstart:install)&#39; \&#xA;        --eval &#39;(ql-util:without-prompting (ql:add-to-init-file))&#39; \&#xA;        --quit&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>I recently wanted to learn about MCP (Model Context Protocol). As someone whose default programming language is Common Lisp, I naturally decided to build an MCP server using Lisp.</p>

<p>Thanks to the creators of <a href="https://github.com/40ants/mcp" rel="nofollow">40ants-mcp</a>, the library provides a nice pattern and code structure that I really like. However, I struggled significantly with installation and getting started. What should have taken minutes ended up taking days.</p>

<p>I&#39;m sharing my experience here so that others who want to build MCP servers in Common Lisp can get started in minutes, not days like I did.</p>

<h2 id="prerequisites" id="prerequisites">Prerequisites</h2>

<p>Before you begin, make sure you have the following installed:</p>
<ul><li><strong>SBCL</strong> – A high-performance Common Lisp compiler</li>
<li><strong>Roswell</strong> – A Common Lisp implementation manager and script runner</li>
<li><strong>Quicklisp</strong> – The de facto package manager for Common Lisp</li>
<li><strong>Ultralisp</strong> – A community-driven distribution of Common Lisp libraries</li></ul>

<h3 id="installing-ultralisp" id="installing-ultralisp">Installing Ultralisp</h3>

<p>In SBCL with Quicklisp, you can enable Ultralisp by:</p>

<pre><code class="language-lisp">(ql-dist:install-dist &#34;http://dist.ultralisp.org/&#34; :prompt nil)
</code></pre>

<p>(How to install SBCL and Quicklisp is in the appendix.)</p>

<h2 id="the-gotcha-loading-40ants-mcp" id="the-gotcha-loading-40ants-mcp">The Gotcha: Loading 40ants-mcp</h2>

<p>Here&#39;s the issue that cost me days: when you try to load <code>40ants-mcp</code> with:</p>

<pre><code class="language-lisp">(ql:quickload :40ants-mcp)
</code></pre>

<p>You might encounter errors. The solution is simple but not obvious—load <code>jsonrpc</code> first:</p>

<pre><code class="language-lisp">(ql:quickload :jsonrpc)
(ql:quickload :40ants-mcp)
</code></pre>

<p>This dependency isn&#39;t automatically resolved, which was the source of my frustration.</p>

<h2 id="creating-your-mcp-server" id="creating-your-mcp-server">Creating Your MCP Server</h2>

<p>Here&#39;s a minimal example from my <code>mcp-exper</code> package:</p>

<pre><code class="language-lisp">(in-package :mcp-exper)

(openrpc-server:define-api (mi-tools :title &#34;mi-tools&#34;))

(40ants-mcp/tools:define-tool (mi-tools add) (a b)
  (:summary &#34;just add&#34;)
  (:param a integer &#34;a&#34;)
  (:param b integer &#34;b&#34;)
  (:result text-content)
  (make-instance &#39;text-content :text (format nil &#34;~a&#34; (+ a b))))

(defun start-server ()
  (40ants-mcp/server/definition:start-server mi-tools))
</code></pre>

<p>Key points:</p>
<ol><li>Use <code>openrpc-server:define-api</code> to define your API</li>
<li>Use <code>40ants-mcp/tools:define-tool</code> to define tools</li>
<li>Return <code>text-content</code> instances for text results (MCP requires specific content types)</li></ol>

<h2 id="running-the-server" id="running-the-server">Running the Server</h2>

<p>Create a Roswell script (<code>mi-mcp-server.ros</code>):</p>

<pre><code class="language-lisp">#!/bin/sh
#|-*- mode:lisp -*-|#
exec ros -Q -- $0 &#34;$@&#34;
|#
(progn
  (ros:ensure-asdf)
  #+quicklisp(ql:quickload &#39;(:mcp-exper) :silent t))

(defun main (&amp;rest argv)
  (declare (ignorable argv))
  (mcp-exper:start-server))
</code></pre>

<h3 id="quick-test" id="quick-test">Quick Test</h3>

<p>Run directly with Roswell:</p>

<pre><code class="language-bash">ros mi-mcp-server.ros
</code></pre>

<h3 id="production-installation" id="production-installation">Production Installation</h3>

<p>Build and install as an executable:</p>

<pre><code class="language-bash">ros build mi-mcp-server.ros
install -m 0755 mi-mcp-server $HOME/.local/bin/
</code></pre>

<p>Make sure <code>$HOME/.local/bin</code> is in your <code>PATH</code>.</p>

<h2 id="integrating-with-opencode" id="integrating-with-opencode">Integrating with Opencode</h2>

<p>To enable your MCP server in opencode, add this to <code>~/.config/opencode/opencode.json</code>:</p>

<pre><code class="language-json">{
    &#34;mcp&#34;: {
        &#34;mi-tools&#34;: {
            &#34;type&#34;: &#34;local&#34;,
            &#34;command&#34;: [&#34;mi-mcp-server&#34;],
            &#34;enabled&#34;: true
        }
    }
}
</code></pre>

<h2 id="conclusion" id="conclusion">Conclusion</h2>

<p>Building MCP servers with Common Lisp is straightforward once you know the tricks. The <code>40ants-mcp</code> library is well-designed, and the OpenRPC integration works smoothly.</p>

<p>I hope this guide saves you the days of frustration I experienced. Happy hacking!</p>

<hr>

<p><em>The full source code for this example is available at <a href="https://codeberg.org/veer66/mcp-exper" rel="nofollow">mcp-exper</a>.</em></p>

<h2 id="appendix-installing-sbcl" id="appendix-installing-sbcl">Appendix: Installing SBCL</h2>

<h3 id="macos" id="macos">macOS</h3>

<pre><code class="language-bash">brew install sbcl
</code></pre>

<h3 id="debian-ubuntu" id="debian-ubuntu">Debian/Ubuntu</h3>

<pre><code class="language-bash">apt install sbcl
</code></pre>

<h3 id="arch-linux" id="arch-linux">Arch Linux</h3>

<pre><code class="language-bash">pacman -S sbcl
</code></pre>

<h2 id="appendix-installing-quicklisp" id="appendix-installing-quicklisp">Appendix: Installing Quicklisp</h2>

<p>Download and install Quicklisp:</p>

<pre><code class="language-bash">wget https://beta.quicklisp.org/quicklisp.lisp
sbcl --load quicklisp.lisp \
        --eval &#39;(quicklisp-quickstart:install)&#39; \
        --eval &#39;(ql-util:without-prompting (ql:add-to-init-file))&#39; \
        --quit
</code></pre>
]]></content:encoded>
      <guid>https://qua.name/veer66/building-an-mcp-server-with-common-lisp-l6jg</guid>
      <pubDate>Sat, 11 Apr 2026 05:00:33 +0200</pubDate>
    </item>
    <item>
      <title>Building an MCP Server with Common Lisp</title>
      <link>https://qua.name/veer66/building-an-mcp-server-with-common-lisp</link>
      <description>&lt;![CDATA[I recently wanted to learn about MCP (Model Context Protocol). As someone whose default programming language is Common Lisp, I naturally decided to build an MCP server using Lisp.&#xA;&#xA;Thanks to the creators of 40ants-mcp, the library provides a nice pattern and code structure that I really like. However, I struggled significantly with installation and getting started. What should have taken minutes ended up taking days.&#xA;&#xA;I&#39;m sharing my experience here so that others who want to build MCP servers in Common Lisp can get started in minutes, not days like I did.&#xA;&#xA;Prerequisites&#xA;&#xA;Before you begin, make sure you have the following installed:&#xA;&#xA;SBCL - A high-performance Common Lisp compiler&#xA;Roswell - A Common Lisp implementation manager and script runner&#xA;Quicklisp - The de facto package manager for Common Lisp&#xA;Ultralisp - A community-driven distribution of Common Lisp libraries&#xA;&#xA;Installing Ultralisp&#xA;&#xA;In SBCL with Quicklisp, you can enable Ultralisp by:&#xA;&#xA;(ql-dist:install-dist &#34;http://dist.ultralisp.org/&#34; :prompt nil)&#xA;&#xA;(How to install SBCL and Quicklisp is in the appendix.)&#xA;&#xA;The Gotcha: Loading 40ants-mcp&#xA;&#xA;Here&#39;s the issue that cost me days: when you try to load 40ants-mcp with:&#xA;&#xA;(ql:quickload :40ants-mcp)&#xA;&#xA;You might encounter errors. The solution is simple but not obvious—load jsonrpc first:&#xA;&#xA;(ql:quickload :jsonrpc)&#xA;(ql:quickload :40ants-mcp)&#xA;&#xA;This dependency isn&#39;t automatically resolved, which was the source of my frustration.&#xA;&#xA;Creating Your MCP Server&#xA;&#xA;Here&#39;s a minimal example from my mcp-exper package:&#xA;&#xA;(in-package :mcp-exper)&#xA;&#xA;(openrpc-server:define-api (mi-tools :title &#34;mi-tools&#34;))&#xA;&#xA;(40ants-mcp/tools:define-tool (mi-tools add) (a b)&#xA;  (:summary &#34;just add&#34;)&#xA;  (:param a integer &#34;a&#34;)&#xA;  (:param b integer &#34;b&#34;)&#xA;  (:result text-content)&#xA;  (make-instance &#39;text-content :text (format nil &#34;~a&#34; (+ a b))))&#xA;&#xA;(defun start-server ()&#xA;  (40ants-mcp/server/definition:start-server mi-tools))&#xA;&#xA;Key points:&#xA;&#xA;Use openrpc-server:define-api to define your API&#xA;Use 40ants-mcp/tools:define-tool to define tools&#xA;Return text-content instances for text results (MCP requires specific content types)&#xA;&#xA;Running the Server&#xA;&#xA;Create a Roswell script (mi-mcp-server.ros):&#xA;&#xA;!/bin/sh&#xA;|-- mode:lisp --|&#xA;exec ros -Q -- $0 &#34;$@&#34;&#xA;|#&#xA;(progn&#xA;  (ros:ensure-asdf)&#xA;  #+quicklisp(ql:quickload &#39;(:mcp-exper) :silent t))&#xA;&#xA;(defun main (&amp;rest argv)&#xA;  (declare (ignorable argv))&#xA;  (mcp-exper:start-server))&#xA;&#xA;Quick Test&#xA;&#xA;Run directly with Roswell:&#xA;&#xA;ros mi-mcp-server.ros&#xA;&#xA;Production Installation&#xA;&#xA;Build and install as an executable:&#xA;&#xA;ros build mi-mcp-server.ros&#xA;install -m 0755 mi-mcp-server $HOME/.local/bin/&#xA;&#xA;Make sure $HOME/.local/bin is in your PATH.&#xA;&#xA;Integrating with Opencode&#xA;&#xA;To enable your MCP server in opencode, add this to ~/.config/opencode/opencode.json:&#xA;&#xA;{&#xA;    &#34;mcp&#34;: {&#xA;        &#34;mi-tools&#34;: {&#xA;            &#34;type&#34;: &#34;local&#34;,&#xA;            &#34;command&#34;: [&#34;mi-mcp-server&#34;],&#xA;            &#34;enabled&#34;: true&#xA;        }&#xA;    }&#xA;}&#xA;&#xA;Conclusion&#xA;&#xA;Building MCP servers with Common Lisp is straightforward once you know the tricks. The 40ants-mcp library is well-designed, and the OpenRPC integration works smoothly.&#xA;&#xA;I hope this guide saves you the days of frustration I experienced. Happy hacking!&#xA;&#xA;---&#xA;&#xA;The full source code for this example is available at mcp-exper.&#xA;&#xA;Appendix: Installing SBCL&#xA;&#xA;macOS&#xA;&#xA;brew install sbcl&#xA;&#xA;Debian/Ubuntu&#xA;&#xA;apt install sbcl&#xA;&#xA;Arch Linux&#xA;&#xA;pacman -S sbcl&#xA;&#xA;Appendix: Installing Quicklisp&#xA;&#xA;Download and install Quicklisp:&#xA;&#xA;wget https://beta.quicklisp.org/quicklisp.lisp&#xA;sbcl --load quicklisp.lisp \&#xA;        --eval &#39;(quicklisp-quickstart:install)&#39; \&#xA;        --eval &#39;(ql-util:without-prompting (ql:add-to-init-file))&#39; \&#xA;        --quit&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>I recently wanted to learn about MCP (Model Context Protocol). As someone whose default programming language is Common Lisp, I naturally decided to build an MCP server using Lisp.</p>

<p>Thanks to the creators of <a href="https://github.com/40ants/mcp" rel="nofollow">40ants-mcp</a>, the library provides a nice pattern and code structure that I really like. However, I struggled significantly with installation and getting started. What should have taken minutes ended up taking days.</p>

<p>I&#39;m sharing my experience here so that others who want to build MCP servers in Common Lisp can get started in minutes, not days like I did.</p>

<h2 id="prerequisites" id="prerequisites">Prerequisites</h2>

<p>Before you begin, make sure you have the following installed:</p>
<ul><li><strong>SBCL</strong> – A high-performance Common Lisp compiler</li>
<li><strong>Roswell</strong> – A Common Lisp implementation manager and script runner</li>
<li><strong>Quicklisp</strong> – The de facto package manager for Common Lisp</li>
<li><strong>Ultralisp</strong> – A community-driven distribution of Common Lisp libraries</li></ul>

<h3 id="installing-ultralisp" id="installing-ultralisp">Installing Ultralisp</h3>

<p>In SBCL with Quicklisp, you can enable Ultralisp by:</p>

<pre><code class="language-lisp">(ql-dist:install-dist &#34;http://dist.ultralisp.org/&#34; :prompt nil)
</code></pre>

<p>(How to install SBCL and Quicklisp is in the appendix.)</p>

<h2 id="the-gotcha-loading-40ants-mcp" id="the-gotcha-loading-40ants-mcp">The Gotcha: Loading 40ants-mcp</h2>

<p>Here&#39;s the issue that cost me days: when you try to load <code>40ants-mcp</code> with:</p>

<pre><code class="language-lisp">(ql:quickload :40ants-mcp)
</code></pre>

<p>You might encounter errors. The solution is simple but not obvious—load <code>jsonrpc</code> first:</p>

<pre><code class="language-lisp">(ql:quickload :jsonrpc)
(ql:quickload :40ants-mcp)
</code></pre>

<p>This dependency isn&#39;t automatically resolved, which was the source of my frustration.</p>

<h2 id="creating-your-mcp-server" id="creating-your-mcp-server">Creating Your MCP Server</h2>

<p>Here&#39;s a minimal example from my <code>mcp-exper</code> package:</p>

<pre><code class="language-lisp">(in-package :mcp-exper)

(openrpc-server:define-api (mi-tools :title &#34;mi-tools&#34;))

(40ants-mcp/tools:define-tool (mi-tools add) (a b)
  (:summary &#34;just add&#34;)
  (:param a integer &#34;a&#34;)
  (:param b integer &#34;b&#34;)
  (:result text-content)
  (make-instance &#39;text-content :text (format nil &#34;~a&#34; (+ a b))))

(defun start-server ()
  (40ants-mcp/server/definition:start-server mi-tools))
</code></pre>

<p>Key points:</p>
<ol><li>Use <code>openrpc-server:define-api</code> to define your API</li>
<li>Use <code>40ants-mcp/tools:define-tool</code> to define tools</li>
<li>Return <code>text-content</code> instances for text results (MCP requires specific content types)</li></ol>

<h2 id="running-the-server" id="running-the-server">Running the Server</h2>

<p>Create a Roswell script (<code>mi-mcp-server.ros</code>):</p>

<pre><code class="language-lisp">#!/bin/sh
#|-*- mode:lisp -*-|#
exec ros -Q -- $0 &#34;$@&#34;
|#
(progn
  (ros:ensure-asdf)
  #+quicklisp(ql:quickload &#39;(:mcp-exper) :silent t))

(defun main (&amp;rest argv)
  (declare (ignorable argv))
  (mcp-exper:start-server))
</code></pre>

<h3 id="quick-test" id="quick-test">Quick Test</h3>

<p>Run directly with Roswell:</p>

<pre><code class="language-bash">ros mi-mcp-server.ros
</code></pre>

<h3 id="production-installation" id="production-installation">Production Installation</h3>

<p>Build and install as an executable:</p>

<pre><code class="language-bash">ros build mi-mcp-server.ros
install -m 0755 mi-mcp-server $HOME/.local/bin/
</code></pre>

<p>Make sure <code>$HOME/.local/bin</code> is in your <code>PATH</code>.</p>

<h2 id="integrating-with-opencode" id="integrating-with-opencode">Integrating with Opencode</h2>

<p>To enable your MCP server in opencode, add this to <code>~/.config/opencode/opencode.json</code>:</p>

<pre><code class="language-json">{
    &#34;mcp&#34;: {
        &#34;mi-tools&#34;: {
            &#34;type&#34;: &#34;local&#34;,
            &#34;command&#34;: [&#34;mi-mcp-server&#34;],
            &#34;enabled&#34;: true
        }
    }
}
</code></pre>

<h2 id="conclusion" id="conclusion">Conclusion</h2>

<p>Building MCP servers with Common Lisp is straightforward once you know the tricks. The <code>40ants-mcp</code> library is well-designed, and the OpenRPC integration works smoothly.</p>

<p>I hope this guide saves you the days of frustration I experienced. Happy hacking!</p>

<hr>

<p><em>The full source code for this example is available at <a href="https://codeberg.org/veer66/mcp-exper" rel="nofollow">mcp-exper</a>.</em></p>

<h2 id="appendix-installing-sbcl" id="appendix-installing-sbcl">Appendix: Installing SBCL</h2>

<h3 id="macos" id="macos">macOS</h3>

<pre><code class="language-bash">brew install sbcl
</code></pre>

<h3 id="debian-ubuntu" id="debian-ubuntu">Debian/Ubuntu</h3>

<pre><code class="language-bash">apt install sbcl
</code></pre>

<h3 id="arch-linux" id="arch-linux">Arch Linux</h3>

<pre><code class="language-bash">pacman -S sbcl
</code></pre>

<h2 id="appendix-installing-quicklisp" id="appendix-installing-quicklisp">Appendix: Installing Quicklisp</h2>

<p>Download and install Quicklisp:</p>

<pre><code class="language-bash">wget https://beta.quicklisp.org/quicklisp.lisp
sbcl --load quicklisp.lisp \
        --eval &#39;(quicklisp-quickstart:install)&#39; \
        --eval &#39;(ql-util:without-prompting (ql:add-to-init-file))&#39; \
        --quit
</code></pre>
]]></content:encoded>
      <guid>https://qua.name/veer66/building-an-mcp-server-with-common-lisp</guid>
      <pubDate>Sat, 11 Apr 2026 05:00:19 +0200</pubDate>
    </item>
    <item>
      <title>Global Variables in Python Are Not That Global</title>
      <link>https://qua.name/veer66/global-variables-in-python-are-not-that-global</link>
      <description>&lt;![CDATA[In the context of programming in the 1980s, &#34;global variables&#34; likely brings to mind languages like MBASIC. However, using MBASIC as an example today would be challenging, as it is now rarely used or known. Instead, GNU Bash, which is the default shell scripting language for many systems—will be used to illustrate what global variables were traditionally like. Anyway, some might think of Fortran II, but I&#39;m not familiar with it.&#xA;&#xA;Bash Example&#xA;&#xA;I wrote a Bash script consisting of four files:&#xA;&#xA;a.bash&#xA;&#xA;a.bash orchestrates everything. &#34;Orchestration&#34; might be too grand a word for these toy scripts, but I want to convey that it performs a role similar to Apache Airflow.&#xA;&#xA;!/bin/bash&#xA;&#xA;. ./b.bash&#xA;. ./c.bash&#xA;. ./d.bash&#xA;&#xA;init&#xA;printcount&#xA;inc&#xA;printcount&#xA;&#xA;b.bash&#xA;&#xA;b.bash contains only one function, inc, which increments the counter, which is the global variable in this example.&#xA;&#xA;inc() {&#xA;    counter=$((counter + 1))&#xA;}&#xA;&#xA;c.bash&#xA;&#xA;c.bash contains printcount, which simply displays the value of the global variable counter.&#xA;&#xA;printcount() {&#xA;    echo $counter&#xA;}&#xA;&#xA;d.bash&#xA;&#xA;In Bash, counter can be initialized globally from within a function by default.&#xA;&#xA;init() {&#xA;    counter=1&#xA;}&#xA;&#xA;Python Example&#xA;&#xA;The following section shows the result of porting the Bash script above to Python, highlighting the key differences.&#xA;&#xA;a.py&#xA;&#xA;This is the orchestration part. Note the use of namespaces or module names.&#xA;&#xA;import c, b, d&#xA;&#xA;d.init()&#xA;c.printcount()&#xA;b.inc()&#xA;c.printcount()&#xA;&#xA;b.py&#xA;&#xA;In the Python version, inc must refer to the module d to access the variable. Alternatively, counter could be explicitly imported.&#xA;&#xA;import d&#xA;&#xA;def inc():&#xA;    d.counter += 1&#xA;&#xA;c.py&#xA;&#xA;Similarly, printcount in Python must also refer to module d.&#xA;&#xA;import d&#xA;&#xA;def printcount():&#xA;    print(d.counter)&#xA;&#xA;d.py&#xA;&#xA;Unlike in Bash, initializing a global variable from within a function—even in the same module—requires an explicit global declaration.&#xA;&#xA;def init():&#xA;    global counter&#xA;    counter = 1&#xA;&#xA;Key Differences&#xA;&#xA;As you can see, a global variable in Bash is truly global across files. In Python, however, a global variable is only global within its module, i.e., file. Furthermore, mutating a global variable in Bash requires no special syntax, whereas in Python a function must explicitly declare global to modify a module-level variable.&#xA;&#xA;Consequently, although both are called &#34;global variables,&#34; Python&#39;s are scoped to the module. This means they won’t interfere with variables in other modules unless we deliberately make them do so. For developers who use one class per module, a Python global variable behaves much like a class variable. Additionally, variable assignment inside a Python function is local by default, preventing accidental modification of global state unless explicitly intended.&#xA;&#xA;In short, many traditional precautions about global variables in languages like Bash or MBASIC no longer apply in Python. Therefore, we might reconsider automatically rejecting global variables based on past advice and instead evaluate their use case thoughtfully.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>In the context of programming in the 1980s, “global variables” likely brings to mind languages like <a href="https://en.wikipedia.org/wiki/MBASIC" rel="nofollow">MBASIC</a>. However, using MBASIC as an example today would be challenging, as it is now rarely used or known. Instead, GNU Bash, which is the default shell scripting language for many systems—will be used to illustrate what global variables were traditionally like. Anyway, some might think of Fortran II, but I&#39;m not familiar with it.</p>

<h2 id="bash-example" id="bash-example">Bash Example</h2>

<p>I wrote a Bash script consisting of four files:</p>

<h3 id="a-bash" id="a-bash">a.bash</h3>

<p><code>a.bash</code> orchestrates everything. “Orchestration” might be too grand a word for these toy scripts, but I want to convey that it performs a role similar to Apache Airflow.</p>

<pre><code class="language-Shell">#!/bin/bash

. ./b.bash
. ./c.bash
. ./d.bash

init
print_count
inc
print_count
</code></pre>

<h3 id="b-bash" id="b-bash">b.bash</h3>

<p><code>b.bash</code> contains only one function, <code>inc</code>, which increments the counter, which is the global variable in this example.</p>

<pre><code class="language-Shell">inc() {
    counter=$((counter + 1))
}
</code></pre>

<h3 id="c-bash" id="c-bash">c.bash</h3>

<p><code>c.bash</code> contains <code>print_count</code>, which simply displays the value of the global variable <code>counter</code>.</p>

<pre><code class="language-Shell">print_count() {
    echo $counter
}
</code></pre>

<h3 id="d-bash" id="d-bash">d.bash</h3>

<p>In Bash, <code>counter</code> can be initialized globally from within a function by default.</p>

<pre><code class="language-Shell">init() {
    counter=1
}
</code></pre>

<h2 id="python-example" id="python-example">Python Example</h2>

<p>The following section shows the result of porting the Bash script above to Python, highlighting the key differences.</p>

<h2 id="a-py" id="a-py">a.py</h2>

<p>This is the orchestration part. Note the use of namespaces or module names.</p>

<pre><code class="language-Python">import c, b, d

d.init()
c.print_count()
b.inc()
c.print_count()
</code></pre>

<h2 id="b-py" id="b-py">b.py</h2>

<p>In the Python version, <code>inc</code> must refer to the module <code>d</code> to access the variable. Alternatively, <code>counter</code> could be explicitly imported.</p>

<pre><code class="language-Python">import d

def inc():
    d.counter += 1
</code></pre>

<h2 id="c-py" id="c-py">c.py</h2>

<p>Similarly, <code>print_count</code> in Python must also refer to module <code>d</code>.</p>

<pre><code class="language-Python">import d

def print_count():
    print(d.counter)
</code></pre>

<h2 id="d-py" id="d-py">d.py</h2>

<p>Unlike in Bash, initializing a global variable from within a function—even in the same module—requires an explicit <code>global</code> declaration.</p>

<pre><code class="language-Python">def init():
    global counter
    counter = 1
</code></pre>

<h2 id="key-differences" id="key-differences">Key Differences</h2>

<p>As you can see, a global variable in Bash is truly global across files. In Python, however, a global variable is only global within its module, i.e., file. Furthermore, mutating a global variable in Bash requires no special syntax, whereas in Python a function must explicitly declare <code>global</code> to modify a module-level variable.</p>

<p>Consequently, although both are called “global variables,” Python&#39;s are scoped to the module. This means they won’t interfere with variables in other modules unless we deliberately make them do so. For developers who use one class per module, a Python global variable behaves much like a class variable. Additionally, variable assignment inside a Python function is local by default, preventing accidental modification of global state unless explicitly intended.</p>

<p>In short, many traditional precautions about global variables in languages like Bash or MBASIC no longer apply in Python. Therefore, we might reconsider automatically rejecting global variables based on past advice and instead evaluate their use case thoughtfully.</p>
]]></content:encoded>
      <guid>https://qua.name/veer66/global-variables-in-python-are-not-that-global</guid>
      <pubDate>Sat, 13 Dec 2025 06:50:10 +0100</pubDate>
    </item>
    <item>
      <title>A lesson about abstraction from Arch Linux</title>
      <link>https://qua.name/veer66/a-lesson-about-abstraction-from-arch-linux</link>
      <description>&lt;![CDATA[I was impressed by pkgsrc because it is a powerful package management system that defines packages using a common tool like a Makefile. I later discovered that the PKGBUILD file is even more impressive, as its purpose is immediately clear. I initially attributed this to my greater familiarity with Bash scripting compared to Makefiles, but I now believe the true reason is PKGBUILD&#39;s level of abstraction.&#xA;&#xA;It retains explicit calls to configure and make, preserving the transparency of a manual installation. This demonstrates that while increased abstraction can make code shorter, it can also hinder understanding.&#xA;&#xA;Another potential advantage of greater abstraction is the ability to change the configure command for every package by modifying just one location. However, since GNU Autotools has continued to use the configure command for decades, it may not be worth sacrificing clarity for this particular benefit.]]&gt;</description>
      <content:encoded><![CDATA[<p>I was impressed by <a href="https://www.pkgsrc.org/" rel="nofollow">pkgsrc</a> because it is a powerful package management system that defines packages using a common tool like a Makefile. I later discovered that the <a href="https://wiki.archlinux.org/title/PKGBUILD" rel="nofollow">PKGBUILD</a> file is even more impressive, as its purpose is immediately clear. I initially attributed this to my greater familiarity with Bash scripting compared to Makefiles, but I now believe the true reason is PKGBUILD&#39;s level of abstraction.</p>

<p>It retains explicit calls to configure and make, preserving the transparency of a manual installation. This demonstrates that while increased abstraction can make code shorter, it can also hinder understanding.</p>

<p>Another potential advantage of greater abstraction is the ability to change the configure command for every package by modifying just one location. However, since GNU Autotools has continued to use the configure command for decades, it may not be worth sacrificing clarity for this particular benefit.</p>
]]></content:encoded>
      <guid>https://qua.name/veer66/a-lesson-about-abstraction-from-arch-linux</guid>
      <pubDate>Sun, 23 Nov 2025 15:05:47 +0100</pubDate>
    </item>
    <item>
      <title>Reasons to use Emacs in 2025</title>
      <link>https://qua.name/veer66/reasons-to-use-emacs-in-2025</link>
      <description>&lt;![CDATA[(Posted at dev.to on 2025-09-02)&#xA;&#xA;Expanding Emacs functionality is as simple as defining a new function instead of creating an entire extension package, as is often done in many other extensible editors. This function can then be re-evaluated, tested, and modified entirely within Emacs using just a few clicks or keyboard shortcuts, with no need to restart or reload Emacs. ]]&gt;</description>
      <content:encoded><![CDATA[<p>(Posted at dev.to on 2025-09-02)</p>

<p>Expanding Emacs functionality is as simple as defining a new function instead of creating an entire extension package, as is often done in many other extensible editors. This function can then be re-evaluated, tested, and modified entirely within Emacs using just a few clicks or keyboard shortcuts, with no need to restart or reload Emacs.</p>
]]></content:encoded>
      <guid>https://qua.name/veer66/reasons-to-use-emacs-in-2025</guid>
      <pubDate>Sun, 23 Nov 2025 11:37:08 +0100</pubDate>
    </item>
    <item>
      <title>Reasons to use Common Lisp in 2025</title>
      <link>https://qua.name/veer66/reasons-to-use-common-lisp-in-2025</link>
      <description>&lt;![CDATA[(Posted at dev.to on 2025-08-17)&#xA;&#xA;An actively-maintained-implementation, long-term-stable-specification programming language&#xA;&#xA;There are many programming languages that don&#39;t change much, including&#xA;Common Lisp, but Common Lisp implementations continue to be developed.&#xA;For example, SBCL (Steel Bank Common Lisp) released its latest version&#xA;just last month.&#xA;&#xA;Common Lisp can be extended through libraries. For example, cl-interpol&#xA;enables Perl-style strings to Common Lisp without requiring a new&#xA;version of Common Lisp. cl-arrows allows Common Lisp to create pipelines&#xA;using Clojure-style syntax without needing to update the Common Lisp&#xA;specification. This exceptional extensibility stems from macro and&#xA;particularly reader macro support in Common Lisp.&#xA;&#xA;Feature-packed&#xA;&#xA;Common Lisp includes many features found in modern programming&#xA;languages, such as:&#xA;&#xA;Garbage collection&#xA;Built-in data structures (e.g., vectors, hash tables)&#xA;Type hints&#xA;Class definitions&#xA;A syntactic structure similar to list comprehensions&#xA;&#xA;Multi-paradigm&#xA;&#xA;While Lisp is commonly associated with functional programming, Common&#xA;Lisp doesn&#39;t enforce this paradigm. It fully supports imperative&#xA;programming (like Pascal), and its object-oriented programming system&#xA;even includes advanced features. Best of all, you can freely mix all&#xA;these styles. Common Lisp even embraces goto-like code via TAGBODY-GO.&#xA;&#xA;Performance&#xA;&#xA;Common Lisp has many implementations, and some of them, such as SBCL,&#xA;are compilers that can generate efficient code.&#xA;&#xA;With some (of course, not all) implementations, many programs written in&#xA;dynamic programming languages run slower than those in static ones, such&#xA;as C and Modula-2.&#xA;&#xA;First, an example of the generated assembly will be shown, along with&#xA;more explanation about why it might be slowed down by some dynamic&#xA;implementations&#xA;&#xA;The code listing below is a part of a program written in Modula-2, which&#xA;must be easy to read by programmers of languages in the extended ALGOL&#xA;family.&#xA;&#xA;    TYPE&#xA;      Book = RECORD&#xA;        title: ARRAY[1..64] OF CHAR;&#xA;        price: REAL;&#xA;      END;&#xA;&#xA;    PROCEDURE SumPrice(a, b: Book): REAL;&#xA;    BEGIN&#xA;      RETURN a.price + b.price;&#xA;    END SumPrice;&#xA;&#xA;The code is mainly for summing the price of books, and only the part&#xA;&#39;a.price + b.price&#39; will be focused on.&#xA;&#xA;&#39;a.price + b.price&#39; is translated into X86-64 assembly code list below&#xA;using the GNU Modula-2 compiler.&#xA;&#xA;    movsd   80(%rbp), %xmm1&#xA;    movsd   152(%rbp), %xmm0&#xA;    addsd   %xmm1, %xmm0&#xA;&#xA;&#34;movsd 80(%rbp), %xmm1&#39; and &#39;movsd 152(%rbp), %xmm0&#39; are for loading&#xA;&#39;prices&#39; to registers &#39;%xmm1&#39; and &#39;%xmm0&#39;, respectively. Finally, &#39;addsd&#xA;%xmm1, %xmm0&#39; is for adding prices together. As can be seen, the prices&#xA;are loaded from exact locations relative to the value of the &#39;%rbp&#39;&#xA;register, which is one of the most efficient ways to load data from&#xA;memory. The instruction &#39;addsd&#39; is used because prices in this program&#xA;are REAL (floating point numbers), and &#39;%xmm0&#39;, &#39;%xmm1&#39;, and &#39;movsd&#39; are&#xA;used for the same reason. This generated code should be reasonably&#xA;efficient. However, the compiler needs to know the type and location of&#xA;the prices beforehand to choose the proper instructions and registers to&#xA;use.&#xA;&#xA;In dynamic languages, &#39;SumPrice&#39; can be applied to a price whose type is&#xA;an INTEGER instead of a REAL, or it can even be a string/text. A&#xA;straightforward implementation would check the type of &#39;a&#39; and &#39;b&#39; at&#xA;runtime, which makes the program much less efficient. The checking and&#xA;especially branching can cost more time than adding the numbers&#xA;themselves. Moreover, obtaining the value of the price attribute from&#xA;&#39;a&#39; and &#39;b&#39; might be done by accessing a hash table instead of directly&#xA;loading the value from memory. Of course, while a hash-table has many&#xA;advantages, it&#39;s less efficient because it requires many steps,&#xA;including comparing the attribute name and generating a hash value.&#xA;&#xA;However, compilers for dynamic languages can be much more advanced than&#xA;what&#39;s mentioned above, and SBCL is one such advanced compiler. SBCL can&#xA;infer types from the code, especially from literals. Moreover, with&#xA;information from type hints and &#39;struct&#39; usage, SBCL can generate code&#xA;that&#39;s comparably as efficient as static language compilers.&#xA;&#xA;Given, the Common Lisp code listing below:&#xA;&#xA;    (defstruct book&#xA;      title&#xA;      (price 0 :type double-float))&#xA;&#xA;    (declaim (ftype (function (book book) double-float) add-price)&#xA;         (optimize (speed 3) (debug 0) (safety 0)))&#xA;    (defun add-price (a b)&#xA;      (+ (book-price a)&#xA;         (book-price b)))&#xA;&#xA;SBCL can generate assembly code for &#39;(+ (book-price a) (book-price b))&#39;&#xA;as shown below:&#xA;&#xA;    ; 86:       F20F104A0D       MOVSD XMM1, [RDX+13]&#xA;    ; 8B:       F20F10570D       MOVSD XMM2, [RDI+13]&#xA;    ; 90:       F20F58D1         ADDSD XMM2, XMM1&#xA;&#xA;The assembly code format is slightly different from the one generated by&#xA;the GNU Modula-2 compiler, but the main parts, the &#39;MOVSD&#39; and &#39;ADDSD&#39;&#xA;instructions and the use of XMM registers—are exactly the same. This&#xA;shows that we can write efficient code in Common Lisp at least for this&#xA;case. This shows that we can write efficient code in Common Lisp, at&#xA;least in this case, that is as efficient as, or nearly as efficient as,&#xA;a static language.&#xA;&#xA;This implies that Common Lisp is good both for high-level rapid&#xA;development and optimized code, which has two advantages: (1) in many&#xA;cases, there is no need to switch between two languages, i.e., a&#xA;high-level one and a fast one; (2) the code can be started from&#xA;high-level and optimized in the same code after a profiler finds&#xA;critical parts. This paradigm can prevent premature optimization.&#xA;&#xA;Interactive programming&#xA;&#xA;Interactive programming may not sound familiar. However, it is a common&#xA;technique that has been used for decades. For example, a database engine&#xA;such as PostgreSQL doesn&#39;t need to be stopped and restarted just to run&#xA;a new SQL statement. Similarly, it is akin to a spreadsheet like Lotus&#xA;1-2-3 or Microsoft Excel, which can run a new formula without needing to&#xA;reload existing sheets or restart the program.&#xA;&#xA;Common Lisp is exceptionally well-suited for interactive programming&#xA;because of (1) integrated editors with a REPL (Read Eval Print Loop),&#xA;(2) the language&#39;s syntax, and (3) the active community that has&#xA;developed libraries specifically designed to support interactive&#xA;programming.&#xA;&#xA;Integrated editors with a REPL&#xA;&#xA;With an integrated with a REPL, any part of the code can be evaluated&#xA;immediately without copying and pasting from an editor into a REPL. This&#xA;workflow provides feedback even faster than hot reloading because the&#xA;code can be evaluated and its results seen instantaneously, even before&#xA;it is saved. There are many supported editors, such as Visual Studio&#xA;Code, Emacs, Neovim, and others.&#xA;&#xA;the language&#39;s syntax&#xA;&#xA;Instead of marking region arbitrarily for evaluating, which is not very&#xA;convenient when it is done every few seconds, in Common Lisp, we can&#xA;mark a form (which is similar to a block in ALGOL) by moving a cursor to&#xA;one of the parentheses in the code, which is very easy with structural&#xA;editing, which will be discussed in the next section.&#xA;&#xA;Moreover, even a method definition can be evaluated immediately without&#xA;resetting the state of the object in Common Lisp. Since method&#xA;definitions are not nested in defclass, this allows mixing interactive&#xA;programming and object-oriented programming (OOP) smoothly.&#xA;&#xA;Here&#39;s the corrected code listing:&#xA;&#xA;    (defclass toto ()&#xA;      ((i :initarg :i :accesor i)))&#xA;&#xA;    (defmethod update-i ((obj toto))&#xA;      (setf (i obj) (+ i obj) 1))&#xA;&#xA;According to the code listing above, the method &#39;update-i&#39; can be&#xA;redefined without interfering with the pre-existing value of &#39;i&#39;.&#xA;&#xA;Structural editing&#xA;&#xA;Instead of editing Lisp code like normal text, tree-based operations can&#xA;be used instead, such as paredit-join-sexps and&#xA;paredit-forward-slurp-sexp. Moving cursor operations, such as&#xA;paredit-forward, which moves the cursor to the end of the form (a&#xA;block). These structural moving operations are also useful for selecting&#xA;regions to be evaluated in a REPL.&#xA;&#xA;Conclusion&#xA;&#xA;In brief, Common Lisp has unparalleled combined advantages, which are&#xA;relevant to software development especially now, not just an archaic&#xA;technology that just came earlier. For example, Forth has a&#xA;long-term-stable specification, and works well with interactive&#xA;programming, but it is not designed for defining classes and adding type&#xA;hints. Julia has similar performance optimization and OOP is even&#xA;richer, but it doesn&#39;t have a long-term-stable specification. Moreover,&#xA;Common Lisp&#39;s community is still active, as libraries, apps, and even&#xA;implementations continue to receive updates.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>(Posted at dev.to on 2025-08-17)</p>

<h2 id="an-actively-maintained-implementation-long-term-stable-specification-programming-language" id="an-actively-maintained-implementation-long-term-stable-specification-programming-language">An actively-maintained-implementation, long-term-stable-specification programming language</h2>

<p>There are many programming languages that don&#39;t change much, including
Common Lisp, but Common Lisp implementations continue to be developed.
For example, SBCL (Steel Bank Common Lisp) released its latest version
just last month.</p>

<p>Common Lisp can be extended through libraries. For example, cl-interpol
enables Perl-style strings to Common Lisp without requiring a new
version of Common Lisp. cl-arrows allows Common Lisp to create pipelines
using Clojure-style syntax without needing to update the Common Lisp
specification. This exceptional extensibility stems from macro and
particularly reader macro support in Common Lisp.</p>

<h2 id="feature-packed" id="feature-packed">Feature-packed</h2>

<p>Common Lisp includes many features found in modern programming
languages, such as:</p>
<ul><li>Garbage collection</li>
<li>Built-in data structures (e.g., vectors, hash tables)</li>
<li>Type hints</li>
<li>Class definitions</li>
<li>A syntactic structure similar to list comprehensions</li></ul>

<h2 id="multi-paradigm" id="multi-paradigm">Multi-paradigm</h2>

<p>While Lisp is commonly associated with functional programming, Common
Lisp doesn&#39;t enforce this paradigm. It fully supports imperative
programming (like Pascal), and its object-oriented programming system
even includes advanced features. Best of all, you can freely mix all
these styles. Common Lisp even embraces goto-like code via TAGBODY-GO.</p>

<h2 id="performance" id="performance">Performance</h2>

<p>Common Lisp has many implementations, and some of them, such as SBCL,
are compilers that can generate efficient code.</p>

<p>With some (of course, not all) implementations, many programs written in
dynamic programming languages run slower than those in static ones, such
as C and Modula-2.</p>

<p>First, an example of the generated assembly will be shown, along with
more explanation about why it might be slowed down by some dynamic
implementations</p>

<p>The code listing below is a part of a program written in Modula-2, which
must be easy to read by programmers of languages in the extended ALGOL
family.</p>

<pre><code class="language-Modula2">    TYPE
      Book = RECORD
        title: ARRAY[1..64] OF CHAR;
        price: REAL;
      END;

    PROCEDURE SumPrice(a, b: Book): REAL;
    BEGIN
      RETURN a.price + b.price;
    END SumPrice;
</code></pre>

<p>The code is mainly for summing the price of books, and only the part
&#39;a.price + b.price&#39; will be focused on.</p>

<p>&#39;a.price + b.price&#39; is translated into X86-64 assembly code list below
using the GNU Modula-2 compiler.</p>

<pre><code class="language-Assembly">    movsd   80(%rbp), %xmm1
    movsd   152(%rbp), %xmm0
    addsd   %xmm1, %xmm0
</code></pre>

<p>“movsd 80(%rbp), %xmm1&#39; and &#39;movsd 152(%rbp), %xmm0&#39; are for loading
&#39;prices&#39; to registers &#39;%xmm1&#39; and &#39;%xmm0&#39;, respectively. Finally, &#39;addsd
%xmm1, %xmm0&#39; is for adding prices together. As can be seen, the prices
are loaded from exact locations relative to the value of the &#39;%rbp&#39;
register, which is one of the most efficient ways to load data from
memory. The instruction &#39;addsd&#39; is used because prices in this program
are REAL (floating point numbers), and &#39;%xmm0&#39;, &#39;%xmm1&#39;, and &#39;movsd&#39; are
used for the same reason. This generated code should be reasonably
efficient. However, the compiler needs to know the type and location of
the prices beforehand to choose the proper instructions and registers to
use.</p>

<p>In dynamic languages, &#39;SumPrice&#39; can be applied to a price whose type is
an INTEGER instead of a REAL, or it can even be a string/text. A
straightforward implementation would check the type of &#39;a&#39; and &#39;b&#39; at
runtime, which makes the program much less efficient. The checking and
especially branching can cost more time than adding the numbers
themselves. Moreover, obtaining the value of the price attribute from
&#39;a&#39; and &#39;b&#39; might be done by accessing a hash table instead of directly
loading the value from memory. Of course, while a hash-table has many
advantages, it&#39;s less efficient because it requires many steps,
including comparing the attribute name and generating a hash value.</p>

<p>However, compilers for dynamic languages can be much more advanced than
what&#39;s mentioned above, and SBCL is one such advanced compiler. SBCL can
infer types from the code, especially from literals. Moreover, with
information from type hints and &#39;struct&#39; usage, SBCL can generate code
that&#39;s comparably as efficient as static language compilers.</p>

<p>Given, the Common Lisp code listing below:</p>

<pre><code class="language-Lisp">    (defstruct book
      title
      (price 0 :type double-float))

    (declaim (ftype (function (book book) double-float) add-price)
         (optimize (speed 3) (debug 0) (safety 0)))
    (defun add-price (a b)
      (+ (book-price a)
         (book-price b)))
</code></pre>

<p>SBCL can generate assembly code for &#39;(+ (book-price a) (book-price b))&#39;
as shown below:</p>

<pre><code>    ; 86:       F20F104A0D       MOVSD XMM1, [RDX+13]
    ; 8B:       F20F10570D       MOVSD XMM2, [RDI+13]
    ; 90:       F20F58D1         ADDSD XMM2, XMM1
</code></pre>

<p>The assembly code format is slightly different from the one generated by
the GNU Modula-2 compiler, but the main parts, the &#39;MOVSD&#39; and &#39;ADDSD&#39;
instructions and the use of XMM registers—are exactly the same. This
shows that we can write efficient code in Common Lisp at least for this
case. This shows that we can write efficient code in Common Lisp, at
least in this case, that is as efficient as, or nearly as efficient as,
a static language.</p>

<p>This implies that Common Lisp is good both for high-level rapid
development and optimized code, which has two advantages: (1) in many
cases, there is no need to switch between two languages, i.e., a
high-level one and a fast one; (2) the code can be started from
high-level and optimized in the same code after a profiler finds
critical parts. This paradigm can prevent premature optimization.</p>

<h2 id="interactive-programming" id="interactive-programming">Interactive programming</h2>

<p>Interactive programming may not sound familiar. However, it is a common
technique that has been used for decades. For example, a database engine
such as PostgreSQL doesn&#39;t need to be stopped and restarted just to run
a new SQL statement. Similarly, it is akin to a spreadsheet like Lotus
1-2-3 or Microsoft Excel, which can run a new formula without needing to
reload existing sheets or restart the program.</p>

<p>Common Lisp is exceptionally well-suited for interactive programming
because of (1) integrated editors with a REPL (Read Eval Print Loop),
(2) the language&#39;s syntax, and (3) the active community that has
developed libraries specifically designed to support interactive
programming.</p>

<h3 id="integrated-editors-with-a-repl" id="integrated-editors-with-a-repl">Integrated editors with a REPL</h3>

<p>With an integrated with a REPL, any part of the code can be evaluated
immediately without copying and pasting from an editor into a REPL. This
workflow provides feedback even faster than hot reloading because the
code can be evaluated and its results seen instantaneously, even before
it is saved. There are many supported editors, such as Visual Studio
Code, Emacs, Neovim, and others.</p>

<h3 id="the-language-s-syntax" id="the-language-s-syntax">the language&#39;s syntax</h3>

<p>Instead of marking region arbitrarily for evaluating, which is not very
convenient when it is done every few seconds, in Common Lisp, we can
mark a form (which is similar to a block in ALGOL) by moving a cursor to
one of the parentheses in the code, which is very easy with structural
editing, which will be discussed in the next section.</p>

<p>Moreover, even a method definition can be evaluated immediately without
resetting the state of the object in Common Lisp. Since method
definitions are not nested in defclass, this allows mixing interactive
programming and object-oriented programming (OOP) smoothly.</p>

<p>Here&#39;s the corrected code listing:</p>

<pre><code class="language-Lisp">    (defclass toto ()
      ((i :initarg :i :accesor i)))

    (defmethod update-i ((obj toto))
      (setf (i obj) (+ i obj) 1))
</code></pre>

<p>According to the code listing above, the method &#39;update-i&#39; can be
redefined without interfering with the pre-existing value of &#39;i&#39;.</p>

<h2 id="structural-editing" id="structural-editing">Structural editing</h2>

<p>Instead of editing Lisp code like normal text, tree-based operations can
be used instead, such as paredit-join-sexps and
paredit-forward-slurp-sexp. Moving cursor operations, such as
paredit-forward, which moves the cursor to the end of the form (a
block). These structural moving operations are also useful for selecting
regions to be evaluated in a REPL.</p>

<h2 id="conclusion" id="conclusion">Conclusion</h2>

<p>In brief, Common Lisp has unparalleled combined advantages, which are
relevant to software development especially now, not just an archaic
technology that just came earlier. For example, Forth has a
long-term-stable specification, and works well with interactive
programming, but it is not designed for defining classes and adding type
hints. Julia has similar performance optimization and OOP is even
richer, but it doesn&#39;t have a long-term-stable specification. Moreover,
Common Lisp&#39;s community is still active, as libraries, apps, and even
implementations continue to receive updates.</p>
]]></content:encoded>
      <guid>https://qua.name/veer66/reasons-to-use-common-lisp-in-2025</guid>
      <pubDate>Sun, 23 Nov 2025 11:35:59 +0100</pubDate>
    </item>
    <item>
      <title>My programming environment journey</title>
      <link>https://qua.name/veer66/my-programming-environment-journey</link>
      <description>&lt;![CDATA[(Published on dev.to on 2025-07-19)&#xA;&#xA;No one actually cares about my programming environment journey, but I’ve often been asked to share it, perhaps for the sake of social media algorithms. I post it here, so later, I can copy and paste this conveniently.&#xA;&#xA;My first computer, in the sense that I, not someone else, made the decision to buy it, ran Debian in 2002. It was a used Compaq desktop with a Pentium II processor, which I bought from Zeer Rangsit, a used computer market that may be the most famous in Thailand these days. When I got it home, I installed Debian right away. Before I bought my computer, I had used MBasic, mainly MS-DOS, Windows 3.1 (though rarely), and  Solaris (remotely).  For experimentation, I used Xenix, AIX, and one on DEC PDP-11 that I forgot.&#xA;&#xA;Since I started with MBasic, that was my first programming environment. I learned Logo at a summer camp, so that became my second. Later, my father bought me a copy of Turbo Basic, and at school, I switched to Turbo Pascal.&#xA;&#xA;After moving to GNU/Linux, I used more editors instead IDEs. From 1995 to 2010, my editors were pico, nvi, vim, TextMate, and Emacs paired with GCC (mostly C, not C++), PHP, Perl, Ruby, Python, JavaScript, and SQL. I also used VisualAge to learn Java in the 90s. I tried Haskell, OCaml, Objective C, Lua, Julia, and Scala too, but it was strictly for learning only.&#xA;&#xA;After 2010, I used IntelliJ IDEA and Eclipse for Java and Kotlin. For Rust (instead of C), I used Emacs and Visual Studio Code. I explored Racket for learning purposes, then later started coding seriously in Clojure and Common Lisp. I tried using Vim 9.x and Neovim too, they were great, but not quite my cup of tea.&#xA;&#xA;In 2025, a few days ago, I learned Smalltalk with Pharo to deepen my understanding of OOP and exploratory programming.&#xA;&#xA;Update 2025/07/20: I forgot to mention xBase. In the &#39;90s, I used it in a programming competition, but none of my programs in xBase reach production.]]&gt;</description>
      <content:encoded><![CDATA[<p>(Published on dev.to on 2025-07-19)</p>

<p>No one actually cares about my programming environment journey, but I’ve often been asked to share it, perhaps for the sake of social media algorithms. I post it here, so later, I can copy and paste this conveniently.</p>

<p>My first computer, in the sense that I, not someone else, made the decision to buy it, ran Debian in 2002. It was a used Compaq desktop with a Pentium II processor, which I bought from Zeer Rangsit, a used computer market that may be the most famous in Thailand these days. When I got it home, I installed Debian right away. Before I bought my computer, I had used MBasic, mainly MS-DOS, Windows 3.1 (though rarely), and  Solaris (remotely).  For experimentation, I used Xenix, AIX, and one on DEC PDP-11 that I forgot.</p>

<p>Since I started with MBasic, that was my first programming environment. I learned Logo at a summer camp, so that became my second. Later, my father bought me a copy of Turbo Basic, and at school, I switched to Turbo Pascal.</p>

<p>After moving to GNU/Linux, I used more editors instead IDEs. From 1995 to 2010, my editors were pico, nvi, vim, TextMate, and Emacs paired with GCC (mostly C, not C++), PHP, Perl, Ruby, Python, JavaScript, and SQL. I also used VisualAge to learn Java in the 90s. I tried Haskell, OCaml, Objective C, Lua, Julia, and Scala too, but it was strictly for learning only.</p>

<p>After 2010, I used IntelliJ IDEA and Eclipse for Java and Kotlin. For Rust (instead of C), I used Emacs and Visual Studio Code. I explored Racket for learning purposes, then later started coding seriously in Clojure and Common Lisp. I tried using Vim 9.x and Neovim too, they were great, but not quite my cup of tea.</p>

<p>In 2025, a few days ago, I learned Smalltalk with Pharo to deepen my understanding of OOP and exploratory programming.</p>

<p>Update 2025/07/20: I forgot to mention xBase. In the &#39;90s, I used it in a programming competition, but none of my programs in xBase reach production.</p>
]]></content:encoded>
      <guid>https://qua.name/veer66/my-programming-environment-journey</guid>
      <pubDate>Sun, 23 Nov 2025 11:34:27 +0100</pubDate>
    </item>
    <item>
      <title>I have been told to avoid linked lists.</title>
      <link>https://qua.name/veer66/i-have-been-told-to-avoid-linked-lists</link>
      <description>&lt;![CDATA[(Published on dev.to on 2025-03-23)&#xA;&#xA;I&#39;ve been told to avoid linked lists because their elements are scattered everywhere, which can be true in some cases. However, I wonder what happens in loops, which I use frequently. I tried to inspect memory addresses of list elements of these two programs run on SBCL.&#xA;&#xA;CL-USER  (setq a-list (let ((a nil)) (push 10 a) (push 20 a) (push 30 a) a))&#xA;(30 20 10)&#xA;CL-USER  (sb-kernel:get-lisp-obj-address a-list)&#xA;69517814583 (37 bits, #x102F95AB37)&#xA;CL-USER  (sb-kernel:get-lisp-obj-address (cdr a-list))&#xA;69517814567 (37 bits, #x102F95AB27)&#xA;CL-USER  (sb-kernel:get-lisp-obj-address (cddr a-list))&#xA;69517814551 (37 bits, #x102F95AB17)&#xA;CL-USER  (sb-kernel:get-lisp-obj-address (cddr a-list))&#xA;69517814551 (37 bits, #x102F95AB17)&#xA;&#xA;CL-USER  (setq a-list (let ((a nil))&#xA;&#x9;&#x9;&#x9;  (push 10 a)&#xA;&#x9;&#x9;&#x9;  (push 20 a)&#xA;&#x9;&#x9;&#x9;  (push 30 a) &#xA;&#x9;&#x9;&#x9;  a))&#xA;(30 20 10)&#xA;CL-USER  (sb-kernel:get-lisp-obj-address a-list)&#xA;69518319943 (37 bits, #x102F9D6147)&#xA;CL-USER  (sb-kernel:get-lisp-obj-address (cdr a-list))&#xA;69518319927 (37 bits, #x102F9D6137)&#xA;CL-USER  (sb-kernel:get-lisp-obj-address (cddr a-list))&#xA;69518319911 (37 bits, #x102F9D6127)&#xA;&#xA;In both programs, the list elements are not scattered. So, if scattered list elements were an issue for these simple cases, you probably used the wrong compiler or memory allocator.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>(Published on dev.to on 2025-03-23)</p>

<p>I&#39;ve been told to avoid linked lists because their elements are scattered everywhere, which can be true in some cases. However, I wonder what happens in loops, which I use frequently. I tried to inspect memory addresses of list elements of these two programs run on SBCL.</p>

<pre><code class="language-Lisp">CL-USER&gt; (setq *a-list* (let ((a nil)) (push 10 a) (push 20 a) (push 30 a) a))
(30 20 10)
CL-USER&gt; (sb-kernel:get-lisp-obj-address *a-list*)
69517814583 (37 bits, #x102F95AB37)
CL-USER&gt; (sb-kernel:get-lisp-obj-address (cdr *a-list*))
69517814567 (37 bits, #x102F95AB27)
CL-USER&gt; (sb-kernel:get-lisp-obj-address (cddr *a-list*))
69517814551 (37 bits, #x102F95AB17)
CL-USER&gt; (sb-kernel:get-lisp-obj-address (cddr *a-list*))
69517814551 (37 bits, #x102F95AB17)
</code></pre>

<pre><code class="language-Lisp">CL-USER&gt; (setq *a-list* (let ((a nil))
			  (push 10 a)
			  (push 20 a)
			  (push 30 a) 
			  a))
(30 20 10)
CL-USER&gt; (sb-kernel:get-lisp-obj-address *a-list*)
69518319943 (37 bits, #x102F9D6147)
CL-USER&gt; (sb-kernel:get-lisp-obj-address (cdr *a-list*))
69518319927 (37 bits, #x102F9D6137)
CL-USER&gt; (sb-kernel:get-lisp-obj-address (cddr *a-list*))
69518319911 (37 bits, #x102F9D6127)
</code></pre>

<p>In both programs, the list elements are not scattered. So, if scattered list elements were an issue for these simple cases, you probably used the wrong compiler or memory allocator.</p>
]]></content:encoded>
      <guid>https://qua.name/veer66/i-have-been-told-to-avoid-linked-lists</guid>
      <pubDate>Sun, 23 Nov 2025 11:32:45 +0100</pubDate>
    </item>
    <item>
      <title>JSON Manipulation</title>
      <link>https://qua.name/veer66/json-manipulation</link>
      <description>&lt;![CDATA[(Posted on dev.to on 2025-01-19)&#xA;&#xA;There are many ways to manipulate JSON. I reviewed a few rapid ways today, which are using a command line tool called jq and libraries that support JSONPath query language.&#xA;&#xA;jq&#xA;&#xA;Awk is a powerful domain-specific language for text processing, but it lacks built-in support for manipulating hierarchical data structures like trees. jq fills this gap by providing a tool for transforming JSON data. However, one potential drawback is that jq requires a separate installation, which may add complexity to my workflow.&#xA;&#xA;So I write a shell script using jq to extract user&#39;s names and user&#39;s urls from a cURL response, and then output it in TSV format.&#xA;&#xA;curl -s &#39;https://mstdn.in.th/api/v1/timelines/public?limit=10&#39; | jq &#39;.[].account | [.username, .url] | @tsv&#39; -r&#xA;&#xA;The result looks like this:&#xA;&#xA;kuketzblog      https://social.tchncs.de/@kuketzblog&#xA;cats    https://social.goose.rodeo/@cats&#xA;AlJazeera       https://flipboard.com/@AlJazeera&#xA;TheHindu        https://flipboard.com/@TheHindu&#xA;GossiTheDog     https://cyberplace.social/@GossiTheDog&#xA;kuketzblog      https://social.tchncs.de/@kuketzblog&#xA;weeklyOSM       https://en.osm.town/@weeklyOSM&#xA;juanbellas      https://masto.es/@juanbellas&#xA;noborusudou     https://misskey.io/@noborusudou&#xA;jerryd  https://mastodon.social/@jerryd&#xA;&#xA;With a TSV file, we can use Awk, sed, etc. to manipulate them as usual.&#xA;&#xA;JSONPath&#xA;&#xA;JSONPath, which was explained in RFC 9535, is supported by many libraries and applications, e.g. PostgreSQL. Still, I try to it in Python by the jsonpathnq library.&#xA;&#xA;from jsonpathng import parse&#xA;import requests&#xA;&#xA;res = requests.get(&#34;https://mstdn.in.th/api/v1/timelines/public?limit=10&#34;)&#xA;&#xA;compiledpath = parse(&#34;$[*].account&#34;)&#xA;&#xA;for matchednode in compiledpath.find(res.json()):&#xA;    print(matchednode.value[&#34;username&#34;] + &#34;\t&#34; + matched_node.value[&#34;url&#34;])&#xA;&#xA;It gave the same result to the shell script above. The code is a bit longer than the shell script. However, it can be integrated with many Python libraries.]]&gt;</description>
      <content:encoded><![CDATA[<p>(Posted on dev.to on 2025-01-19)</p>

<p>There are many ways to manipulate JSON. I reviewed a few rapid ways today, which are using a command line tool called <code>jq</code> and libraries that support JSONPath query language.</p>

<h1 id="jq" id="jq">jq</h1>

<p><code>Awk</code> is a powerful domain-specific language for text processing, but it lacks built-in support for manipulating hierarchical data structures like trees. <code>jq</code> fills this gap by providing a tool for transforming JSON data. However, one potential drawback is that <code>jq</code> requires a separate installation, which may add complexity to my workflow.</p>

<p>So I write a shell script using <code>jq</code> to extract user&#39;s names and user&#39;s urls from a cURL response, and then output it in TSV format.</p>

<pre><code class="language-sh">curl -s &#39;https://mstdn.in.th/api/v1/timelines/public?limit=10&#39; | jq &#39;.[].account | [.username, .url] | @tsv&#39; -r
</code></pre>

<p>The result looks like this:</p>

<pre><code class="language-tsv">kuketzblog      https://social.tchncs.de/@kuketzblog
cats    https://social.goose.rodeo/@cats
AlJazeera       https://flipboard.com/@AlJazeera
TheHindu        https://flipboard.com/@TheHindu
GossiTheDog     https://cyberplace.social/@GossiTheDog
kuketzblog      https://social.tchncs.de/@kuketzblog
weeklyOSM       https://en.osm.town/@weeklyOSM
juanbellas      https://masto.es/@juanbellas
noborusudou     https://misskey.io/@noborusudou
jerryd  https://mastodon.social/@jerryd
</code></pre>

<p>With a TSV file, we can use Awk, sed, etc. to manipulate them as usual.</p>

<h1 id="jsonpath" id="jsonpath">JSONPath</h1>

<p>JSONPath, which was explained in <a href="https://www.rfc-editor.org/rfc/rfc9535" rel="nofollow">RFC 9535</a>, is supported by many libraries and applications, e.g. PostgreSQL. Still, I try to it in Python by the <code>jsonpath_nq</code> library.</p>

<pre><code class="language-Python">from jsonpath_ng import parse
import requests


res = requests.get(&#34;https://mstdn.in.th/api/v1/timelines/public?limit=10&#34;)

compiled_path = parse(&#34;$[*].account&#34;)


for matched_node in compiled_path.find(res.json()):
    print(matched_node.value[&#34;username&#34;] + &#34;\t&#34; + matched_node.value[&#34;url&#34;])
</code></pre>

<p>It gave the same result to the shell script above. The code is a bit longer than the shell script. However, it can be integrated with many Python libraries.</p>
]]></content:encoded>
      <guid>https://qua.name/veer66/json-manipulation</guid>
      <pubDate>Sun, 23 Nov 2025 11:31:27 +0100</pubDate>
    </item>
    <item>
      <title>Four Reasons that I Sometimes Use Awk Instead of Python</title>
      <link>https://qua.name/veer66/four-reasons-that-i-sometimes-use-awk-instead-of-python</link>
      <description>&lt;![CDATA[Python is a fantastic language, but in specific situations, Awk can offer significant advantages, particularly in terms of portability, longevity, conciseness, and interoperability.&#xA;&#xA;While Python scripts are generally portable, they may not always run seamlessly on popular Docker base images like Debian and Alpine. In contrast, Awk scripts are often readily available and executable within these environments.&#xA;&#xA;Although Python syntax is relatively stable, its lifespan is shorter compared to Awk. For example, the print 10 syntax from the early 2000s is no longer valid in modern Python. However, Awk scripts from the 1980s can still be executed in current environments.&#xA;&#xA;Python is known for its conciseness, especially when compared to languages like Java. However, when it comes to text processing and working within shell pipelines, Awk often provides more concise solutions. For instance, extracting text blocks between &#34;REPORT&#34; and &#34;END&#34; can be achieved with a single line in Awk: /REPORT/,/END/ { print }. Achieving the same result in Python typically involves more lines of code, including handling file input and pattern matching.&#xA;&#xA;While Python can be embedded within shell scripts like Bash, aligning the indentation of multiline Python code with the surrounding shell script can often break the Python syntax. Awk, on the other hand, is less sensitive to indentation, making it easier to integrate into shell scripts.&#xA;&#xA;Although different Awk implementations (such as Busybox Awk and GNU Awk) may have minor variations, Awk generally offers advantages over Python in the situations mentioned above.]]&gt;</description>
      <content:encoded><![CDATA[<p>Python is a fantastic language, but in specific situations, Awk can offer significant advantages, particularly in terms of portability, longevity, conciseness, and interoperability.</p>

<p>While Python scripts are generally portable, they may not always run seamlessly on popular Docker base images like Debian and Alpine. In contrast, Awk scripts are often readily available and executable within these environments.</p>

<p>Although Python syntax is relatively stable, its lifespan is shorter compared to Awk. For example, the <code>print 10</code> syntax from the early 2000s is no longer valid in modern Python. However, Awk scripts from the 1980s can still be executed in current environments.</p>

<p>Python is known for its conciseness, especially when compared to languages like Java. However, when it comes to text processing and working within shell pipelines, Awk often provides more concise solutions. For instance, extracting text blocks between “REPORT” and “END” can be achieved with a single line in Awk: <code>/REPORT/,/END/ { print }</code>. Achieving the same result in Python typically involves more lines of code, including handling file input and pattern matching.</p>

<p>While Python can be embedded within shell scripts like Bash, aligning the indentation of multiline Python code with the surrounding shell script can often break the Python syntax. Awk, on the other hand, is less sensitive to indentation, making it easier to integrate into shell scripts.</p>

<p>Although different Awk implementations (such as Busybox Awk and GNU Awk) may have minor variations, Awk generally offers advantages over Python in the situations mentioned above.</p>
]]></content:encoded>
      <guid>https://qua.name/veer66/four-reasons-that-i-sometimes-use-awk-instead-of-python</guid>
      <pubDate>Sun, 05 Jan 2025 17:28:59 +0100</pubDate>
    </item>
  </channel>
</rss>