<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>metafunctor</title><link>https://metafunctor.com/</link><description>Research Engineer &amp; Computer Scientist - Machine Learning, Statistical Computing, Open Source Development</description><language>en-us</language><managingEditor>lex@metafunctor.com (Alex Towell)</managingEditor><webMaster>lex@metafunctor.com (Alex Towell)</webMaster><lastBuildDate>Fri, 10 Jul 2026 09:10:23 -0500</lastBuildDate><atom:link href="https://metafunctor.com/" rel="self" type="application/rss+xml"/><image><url>https://metafunctor.com/images/og-image.jpg</url><title>metafunctor</title><link>https://metafunctor.com/</link></image><item><title>Reverse-Process Synthetic Data Generation for Math Reasoning</title><link>https://metafunctor.com/post/rpsdg/</link><pubDate>Tue, 25 Jun 2024 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/post/rpsdg/</guid><description>Training LLMs on mathematical reasoning by inverting easy-to-solve problems: generate derivatives, reverse them into integration exercises with full step-by-step solutions.</description><content:encoded><![CDATA[<p>Check out the (early) project and source code on <a href="https://github.com/queelius/RPSDG">GitHub</a>.</p>
<h2 id="the-idea">The idea</h2>
<p>Some problems are easy in one direction and hard in the other. Taking a derivative is mechanical. Finding an antiderivative can require genuine creativity. Generating a random expression and verifying a proof is easy. Discovering the proof is hard.</p>
<p>RPSDG (Reverse-Process Synthetic Data Generation) exploits this asymmetry. Run the easy direction with full step-by-step work, then reverse the result to get a hard problem with a known solution. You end up with process-supervised training data: not just the answer, but the entire derivation.</p>
<p>Richard Sutton&rsquo;s &ldquo;The Bitter Lesson&rdquo; argues that methods scaling with compute and data will eventually win. The bottleneck is high-quality data. A lot of the world&rsquo;s data is latent, the processes that generated it are not written down. In math, the way a proof was discovered is usually hidden behind a polished presentation. RPSDG is one way to manufacture that hidden process data.</p>
<h2 id="derivatives-to-integrals">Derivatives to Integrals</h2>
<p>Computing derivatives is mechanical. Integration often is not. That asymmetry gives us a data pipeline.</p>
<ol>
<li>
<p><strong>Start with known functions.</strong> Pick functions \( f(x) \) with closed-form derivatives: polynomials, trig, exponentials, logarithms. Vary complexity.</p>
</li>
<li>
<p><strong>Differentiate with full work shown.</strong> Take the derivative of \( f(x) \) to get \( f'(x) \), recording every step.</p>
</li>
<li>
<p><strong>Reverse the process.</strong> Now \( f'(x) \) is the problem and \( f(x) \) is the solution. The recorded steps, read backward, give you a worked integration example.</p>
</li>
</ol>
<p>By composing functions of varying complexity, you get integration problems of graduating difficulty. The training data comes with step-by-step solutions for free, because you generated it by running the easy direction.</p>
<h2 id="proofs-by-random-walk">Proofs by Random Walk</h2>
<p>The same idea works for theorem proving. Generating proofs is hard. Verifying them is (comparatively) easy.</p>
<ol>
<li>
<p><strong>Random walks in expression space.</strong> Start with a random expression \( e_{\text{start}} \). Apply rewrite rules \( r_1, r_2, \ldots, r_n \) to get a chain of intermediate expressions ending at \( e_{\text{end}} \).</p>
</li>
<li>
<p><strong>Read off the theorem.</strong> The pair \( (e_{\text{start}}, e_{\text{end}}) \) is a theorem. The chain of rewrites is its proof.</p>
</li>
<li>
<p><strong>Reverse when useful.</strong> Running the chain backward works too, especially when a complex step in one direction (integration) becomes simple in the other (differentiation).</p>
</li>
<li>
<p><strong>Scale it.</strong> Random starting points and random rewrite sequences give you a diverse set of theorems and proofs automatically. No human has to come up with the theorem first.</p>
</li>
</ol>
<h2 id="what-this-gets-you">What this gets you</h2>
<p>The training data has process supervision baked in. Every example includes intermediate steps, not just the final answer. That should help LLMs learn multi-step reasoning rather than pattern-matching to answers.</p>
<p>It also gives you something like explainability for free: the model&rsquo;s training data literally consists of step-by-step solutions, so the model has a better chance of producing step-by-step reasoning at inference time.</p>
<h2 id="limitations-and-next-steps">Limitations and next steps</h2>
<p>This is early work. The data generation pipeline exists, but I have not yet run the full fine-tuning experiments and benchmarks. The planned pipeline is:</p>
<ul>
<li>Data generation with graduating difficulty (curriculum learning)</li>
<li>Fine-tuning transformer-based LMs on the generated data</li>
<li>Self-supervised learning experiments</li>
<li>Evaluation against standard math reasoning benchmarks</li>
</ul>
<p>Further out, I want to explore reinforcement learning for rewarding multi-step reasoning even when the solution is not known in advance but can be verified.</p>
<h2 id="related">Related</h2>
<p>The verification asymmetry exploited here shows up at multiple levels.
At inference time, it enables tree search over LLM solutions:
I Spent $0.48 to Find Out When MCTS Actually Works for LLM Reasoning.
At the level of scientific method, it&rsquo;s the core of
<a href="/post/2025-01-05-science-as-verifiable-search/">Science as Verifiable Search</a>:
cheap testing enables fast iteration through hypothesis space.</p>]]></content:encoded><media:content url="https://metafunctor.com/post/rpsdg/card.jpg" medium="image"><media:title type="plain"/></media:content><category>artificial intelligence</category><category>machine learning</category><category>mathematics</category><category>algebra</category><category>calculus</category><category>LLMs</category><category>synthetic data</category><category>data generation</category><category>reasoning</category><category>AI training</category><category>explainable AI</category></item><item><title>SLUUG Talk: Demystifying Large Language Models on Linux</title><link>https://metafunctor.com/post/gave-a-presentation-for-sluug-about-llms/</link><pubDate>Fri, 23 Feb 2024 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/post/gave-a-presentation-for-sluug-about-llms/</guid><description>Talk for the St. Louis Unix Users Group about running and understanding Large Language Models on Linux.</description><content:encoded><![CDATA[<p>I gave a talk for the St. Louis Unix Users Group (SLUUG) titled &ldquo;Demystifying Large Language Models (LLMs) on Linux: From Theory to Application.&rdquo; The goal was to walk through how LLMs actually work and how to run them locally on Linux.</p>
<p>I demoed two projects:</p>
<ol>
<li>
<p>A simple Colab notebook using basic Python to generate text with an n-gram model. The point was to illustrate the core idea behind language models and show concretely why n-grams fall short, which motivates the transformer architecture.</p>
</li>
<li>
<p>A project that uses ElasticSearch and LLMs to allow natural language search queries over databases.</p>
</li>
</ol>
<p>The talk went well. Content and code are on <a href="https://github.com/queelius/sluug-talk-llm">GitHub</a>.</p>
]]></content:encoded><media:content url="https://metafunctor.com/post/gave-a-presentation-for-sluug-about-llms/card.jpg" medium="image"><media:title type="plain"/></media:content><category>llm</category><category>linux</category><category>ai</category><category>presentation</category></item><item><title>Master's Project: Reliability Estimation in Series Systems</title><link>https://metafunctor.com/post/masters-stats-siue-proj/</link><pubDate>Mon, 19 Feb 2024 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/post/masters-stats-siue-proj/</guid><description>My master's project on maximum likelihood estimation for series systems with right-censored and masked failure data.</description><content:encoded><![CDATA[<p>I presented my master&rsquo;s project in October 2023, finishing up my MS in statistics/mathematics at SIUE. The associated paper is titled &ldquo;Reliability Estimation in Series Systems: Maximum Likelihood Techniques for Right-Censored and Masked Failure Data.&rdquo;</p>
<h2 id="the-problem">The Problem</h2>
<p>In reliability engineering, you often find yourself in an annoying situation: a system fails, but you do not know which component caused the failure. This is called <em>masked failure data</em>. On top of that, some systems are still running when you stop observing them, so you only know they survived at least that long. That is <em>right censoring</em>. Both are common in practice. Identifying the exact failed component is expensive or sometimes impossible.</p>
<p>The project builds a likelihood-based framework that handles both masking and censoring simultaneously, models component lifetimes with Weibull distributions, derives closed-form Fisher information for the exponential special case, and provides bootstrap methods for uncertainty quantification. I implemented it all in an R package so practitioners can actually use it.</p>
<h2 id="related-work">Related Work</h2>
<p>This connects to several other posts and projects:</p>
<ul>
<li><a href="/post/2024-04-15-expo-masked-fim/">Closed-Form Results for Masked Exponential Series Systems</a> covers the exponential distribution special case with analytical solutions</li>
<li><a href="/projects/likelihood.model/">likelihood.model R package</a> is the software implementation</li>
</ul>
<p>See the full project page <a href="/projects/reliability-estimation-in-series-systems/">here</a>.</p>
]]></content:encoded><media:content url="https://metafunctor.com/post/masters-stats-siue-proj/featured.png" medium="image"><media:title type="plain"/></media:content><category>series systems</category><category>masked failure data</category><category>censoring</category><category>reliability analysis</category><category>maximum likelihood</category><category>Weibull distribution</category><category>R</category></item><item><title>Building an Immutable, Content-Addressed Filesystem in Python</title><link>https://metafunctor.com/projects/dagshell/posts/01-immutable-content-addressed-filesystem/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/projects/dagshell/posts/01-immutable-content-addressed-filesystem/</guid><description>How Git-style content addressing creates elegant, functional data structures</description><content:encoded><![CDATA[<p>When you run <code>git commit</code>, something interesting happens: Git doesn&rsquo;t store your files by name. Instead, it computes a SHA-1 hash of each file&rsquo;s content and stores the file under that hash. The filename is just a pointer to the hash. This is <strong>content addressing</strong>—identifying data by what it contains rather than where it lives.</p>
<p>This seemingly simple idea has profound implications. In this post, I&rsquo;ll show how to build a content-addressed virtual filesystem in Python, exploring how immutability and content addressing work together to create elegant, functional data structures.</p>
<h2 id="why-content-addressing">Why Content Addressing?</h2>
<p>Consider a traditional filesystem. When you modify a file, the system overwrites the old content. The file&rsquo;s identity (its path) stays the same, but its content changes. This mutable approach has problems:</p>
<ol>
<li><strong>No automatic history</strong>: Once you overwrite, the old data is gone</li>
<li><strong>No deduplication</strong>: Two identical files take up twice the space</li>
<li><strong>No integrity verification</strong>: Corruption can go undetected</li>
</ol>
<p>Content addressing solves all three. If a file&rsquo;s identity <em>is</em> its content (via a hash), then:</p>
<ol>
<li><strong>History is preserved</strong>: Changing content creates a new hash, so the old version still exists</li>
<li><strong>Deduplication is automatic</strong>: Identical content has identical hashes—stored once</li>
<li><strong>Integrity is built-in</strong>: If the content doesn&rsquo;t match the hash, you know something&rsquo;s wrong</li>
</ol>
<h2 id="the-node-hierarchy">The Node Hierarchy</h2>
<p>Let&rsquo;s build this. First, we define our filesystem nodes using Python&rsquo;s frozen dataclasses:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">hashlib</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@dataclass</span><span class="p">(</span><span class="n">frozen</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Node</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Base class for all filesystem nodes.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">mode</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="cl">    <span class="n">uid</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">1000</span>
</span></span><span class="line"><span class="cl">    <span class="n">gid</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">1000</span>
</span></span><span class="line"><span class="cl">    <span class="n">mtime</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">compute_hash</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Compute SHA256 hash of this node including all metadata.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">to_dict</span><span class="p">(),</span> <span class="n">sort_keys</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">encode</span><span class="p">())</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">()</span>
</span></span></code></pre></div><p>The <code>frozen=True</code> parameter is crucial. It makes instances immutable—you cannot modify a Node after creation. Any &ldquo;change&rdquo; requires creating a new Node.</p>
<p>We then specialize for different node types:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@dataclass</span><span class="p">(</span><span class="n">frozen</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">FileNode</span><span class="p">(</span><span class="n">Node</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Regular file node.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">content</span><span class="p">:</span> <span class="nb">bytes</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@dataclass</span><span class="p">(</span><span class="n">frozen</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DirNode</span><span class="p">(</span><span class="n">Node</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Directory node containing references to child nodes.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">children</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">dict</span><span class="p">)</span>  <span class="c1"># name -&gt; hash</span>
</span></span></code></pre></div><p>Notice that <code>DirNode.children</code> maps names to <em>hashes</em>, not to Node objects directly. This is the key insight: directories don&rsquo;t contain files; they contain <em>references</em> to file hashes. The actual nodes live in a separate store.</p>
<h2 id="the-dag-structure">The DAG Structure</h2>
<p>This reference-based approach creates a Directed Acyclic Graph (DAG):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">FileSystem</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Content-addressable virtual filesystem.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># The DAG: hash -&gt; Node</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Node</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Path index: absolute path -&gt; hash</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">paths</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_add_node</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">node</span><span class="p">:</span> <span class="n">Node</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Add a node to the DAG, returning its hash.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">node_hash</span> <span class="o">=</span> <span class="n">node</span><span class="o">.</span><span class="n">compute_hash</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">node_hash</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">node_hash</span><span class="p">]</span> <span class="o">=</span> <span class="n">node</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">node_hash</span>
</span></span></code></pre></div><p>When we add a node, we compute its hash and store the mapping <code>hash → node</code>. If an identical node already exists (same hash), we don&rsquo;t duplicate it—we just return the existing hash. <strong>Deduplication is automatic.</strong></p>
<h2 id="immutable-updates">Immutable Updates</h2>
<p>Here&rsquo;s where immutability shines. When we write to a file, we don&rsquo;t modify anything. Instead, we:</p>
<ol>
<li>Create a new FileNode with the new content</li>
<li>Create a new DirNode for the parent, pointing to the new file hash</li>
<li>Update the path index</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">write</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">bytes</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Write content to a file.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">parent_path</span><span class="p">,</span> <span class="n">name</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_parent_path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">parent_hash</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">paths</span><span class="p">[</span><span class="n">parent_path</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">parent</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">parent_hash</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Create new file node</span>
</span></span><span class="line"><span class="cl">    <span class="n">file_node</span> <span class="o">=</span> <span class="n">FileNode</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">file_hash</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_add_node</span><span class="p">(</span><span class="n">file_node</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Create new parent directory with updated child reference</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_children</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">parent</span><span class="o">.</span><span class="n">children</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_children</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">file_hash</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_parent</span> <span class="o">=</span> <span class="n">DirNode</span><span class="p">(</span><span class="n">children</span><span class="o">=</span><span class="n">new_children</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_parent_hash</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_add_node</span><span class="p">(</span><span class="n">new_parent</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Update path index</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">paths</span><span class="p">[</span><span class="n">parent_path</span><span class="p">]</span> <span class="o">=</span> <span class="n">new_parent_hash</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">paths</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="n">file_hash</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">True</span>
</span></span></code></pre></div><p>The old FileNode still exists in <code>self.nodes</code>. The old DirNode still exists too. We&rsquo;ve just created new versions and updated where the path points. This is <strong>structural sharing</strong>—unchanged parts of the tree are shared between versions.</p>
<h2 id="visualizing-the-dag">Visualizing the DAG</h2>
<p>Let&rsquo;s trace through an example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">fs</span> <span class="o">=</span> <span class="n">FileSystem</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">fs</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="s2">&#34;/project&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">fs</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">&#34;/project/main.py&#34;</span><span class="p">,</span> <span class="sa">b</span><span class="s2">&#34;print(&#39;hello&#39;)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">fs</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">&#34;/project/main.py&#34;</span><span class="p">,</span> <span class="sa">b</span><span class="s2">&#34;print(&#39;world&#39;)&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>After these operations, our DAG contains:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Hash: a1b2c3... → DirNode(children={})           # original /project
</span></span><span class="line"><span class="cl">Hash: d4e5f6... → FileNode(&#34;print(&#39;hello&#39;)&#34;)     # first version
</span></span><span class="line"><span class="cl">Hash: g7h8i9... → DirNode(children={&#34;main.py&#34;: &#34;d4e5f6...&#34;})
</span></span><span class="line"><span class="cl">Hash: j0k1l2... → FileNode(&#34;print(&#39;world&#39;)&#34;)     # second version
</span></span><span class="line"><span class="cl">Hash: m3n4o5... → DirNode(children={&#34;main.py&#34;: &#34;j0k1l2...&#34;})
</span></span></code></pre></div><p>Both versions of <code>main.py</code> exist. The path <code>/project/main.py</code> points to the latest hash (<code>j0k1l2...</code>), but we could easily restore the old version if we tracked which hashes corresponded to which versions.</p>
<h2 id="benefits-in-practice">Benefits in Practice</h2>
<p>This design enables powerful features almost for free:</p>
<p><strong>Snapshots</strong>: Save the current <code>paths</code> dictionary. Restore it later to go back in time.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">snapshot</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Create a snapshot of the current filesystem state.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">paths</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">restore</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">snapshot</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Restore filesystem to a previous snapshot.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">paths</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">snapshot</span><span class="p">)</span>
</span></span></code></pre></div><p><strong>Deduplication</strong>: Multiple paths can point to the same hash.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># These might share the same underlying node if content is identical</span>
</span></span><span class="line"><span class="cl"><span class="n">fs</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">&#34;/file1.txt&#34;</span><span class="p">,</span> <span class="sa">b</span><span class="s2">&#34;hello&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">fs</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">&#34;/file2.txt&#34;</span><span class="p">,</span> <span class="sa">b</span><span class="s2">&#34;hello&#34;</span><span class="p">)</span>  <span class="c1"># Same hash, no new storage</span>
</span></span></code></pre></div><p><strong>Integrity checking</strong>: If someone asks for a file, we can verify it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">verify</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Verify a file&#39;s integrity.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">node_hash</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">paths</span><span class="p">[</span><span class="n">path</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">node</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">node_hash</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">node</span><span class="o">.</span><span class="n">compute_hash</span><span class="p">()</span> <span class="o">==</span> <span class="n">node_hash</span>
</span></span></code></pre></div><h2 id="the-functional-programming-connection">The Functional Programming Connection</h2>
<p>This approach is deeply connected to functional programming. In FP:</p>
<ul>
<li>Data is immutable</li>
<li>&ldquo;Changes&rdquo; create new values</li>
<li>Sharing is safe because nothing mutates</li>
</ul>
<p>Our filesystem follows these principles exactly. Nodes are frozen. &ldquo;Writing&rdquo; creates new nodes. Multiple paths can safely share nodes because nodes never change.</p>
<p>This is why Clojure&rsquo;s persistent data structures, Haskell&rsquo;s pure values, and Git&rsquo;s object store all use similar ideas. <strong>Content addressing + immutability = safe, efficient, verifiable data.</strong></p>
<h2 id="trade-offs">Trade-offs</h2>
<p>Nothing is free. This approach has costs:</p>
<ol>
<li>
<p><strong>Memory</strong>: Old versions accumulate. You need garbage collection to reclaim space from unreachable nodes.</p>
</li>
<li>
<p><strong>Performance</strong>: Creating new nodes for every change can be slower than in-place mutation for write-heavy workloads.</p>
</li>
<li>
<p><strong>Complexity</strong>: Path resolution requires extra indirection through the hash table.</p>
</li>
</ol>
<p>For many use cases—especially those valuing history, integrity, and safe concurrency—these trade-offs are worthwhile.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Content addressing transforms how we think about data. Instead of &ldquo;where is this file?&rdquo; we ask &ldquo;what is this content&rsquo;s identity?&rdquo; Instead of destructive updates, we create new versions while sharing unchanged structure.</p>
<p>This pattern appears everywhere: Git, IPFS, Nix, Docker layers, and many database internals. Understanding it opens doors to building robust, elegant systems.</p>
<p>The full implementation in <a href="https://github.com/queelius/dagshell">DagShell</a> extends these ideas with a complete POSIX-like interface, demonstrating how content addressing can underpin a full virtual filesystem.</p>
<hr>
<p><em>Next in this series: <a href="../02-unix-philosophy-python/">Unix Philosophy in Python</a> — building composable commands with method chaining.</em></p>
]]></content:encoded></item><item><title>Fine-Tuning a Tiny LLM for ElasticSearch DSL</title><link>https://metafunctor.com/post/llm-fine-tuning-es-dsl/</link><pubDate>Mon, 19 Feb 2024 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/post/llm-fine-tuning-es-dsl/</guid><description>Fine-tuning a small language model to generate ElasticSearch DSL queries from natural language, as a proof of concept for domain-specific LLM specialization.</description><content:encoded><![CDATA[<p>I am fine-tuning a small LLM to generate ElasticSearch DSL queries from natural language. The project is on <a href="https://github.com/queelius/elasticsearch-lm">GitHub</a>.</p>
<p>The idea: take a task that large models handle well (translating &ldquo;find all orders over $100 from last month&rdquo; into the corresponding ElasticSearch JSON query) and see if a tiny model can learn it from synthetic data.</p>
<p>The data pipeline works like this. I started by generating examples from GPT-4. Then I wrote a script that samples from those outputs and uses them as few-shot examples for Mistral, which generates a much larger synthetic dataset. The next step is reshaping the data into the expected format and fine-tuning.</p>
<p>This is early stage. The synthetic data exists, the fine-tuning has not happened yet. I will update this post with results.</p>
<p>If you are interested in collaborating on this or related projects, email me at <a href="mailto:lex@metafunctor.com">lex@metafunctor.com</a>.</p>
]]></content:encoded><media:content url="https://metafunctor.com/post/llm-fine-tuning-es-dsl/card.jpg" medium="image"><media:title type="plain"/></media:content><category>large language models</category><category>fine-tuning</category><category>information retrieval</category><category>elastic search</category><category>domain-specific language</category><category>json</category></item><item><title>Unix Philosophy in Python: Composable Commands with Method Chaining</title><link>https://metafunctor.com/projects/dagshell/posts/02-unix-philosophy-python/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/projects/dagshell/posts/02-unix-philosophy-python/</guid><description>How to build pipeable, chainable interfaces that do one thing well</description><content:encoded><![CDATA[<p>The Unix philosophy, articulated by Doug McIlroy, can be summarized as:</p>
<blockquote>
<p>Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.</p>
</blockquote>
<p>This philosophy gave us pipes (<code>|</code>), small focused utilities (<code>cat</code>, <code>grep</code>, <code>sort</code>), and the ability to compose complex operations from simple parts:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat access.log <span class="p">|</span> grep <span class="s2">&#34;404&#34;</span> <span class="p">|</span> cut -d<span class="s1">&#39; &#39;</span> -f1 <span class="p">|</span> sort <span class="p">|</span> uniq -c <span class="p">|</span> sort -rn
</span></span></code></pre></div><p>Can we bring this composability to Python? Not just subprocess calls, but native Python objects that pipe and chain as naturally as Unix commands? Let&rsquo;s build it.</p>
<h2 id="the-problem-with-methods">The Problem with Methods</h2>
<p>Standard Python methods have a composition problem. Consider:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># We want to: read a file, filter lines, count words</span>
</span></span><span class="line"><span class="cl"><span class="n">content</span> <span class="o">=</span> <span class="n">read_file</span><span class="p">(</span><span class="s2">&#34;/data/log.txt&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">filtered</span> <span class="o">=</span> <span class="n">grep</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="s2">&#34;ERROR&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">count</span> <span class="o">=</span> <span class="n">wc</span><span class="p">(</span><span class="n">filtered</span><span class="p">)</span>
</span></span></code></pre></div><p>This works, but it&rsquo;s inside-out compared to how we think about it. We think &ldquo;read, then filter, then count&rdquo;—a left-to-right pipeline. But we write it bottom-up with intermediate variables.</p>
<p>Method chaining helps:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">read_file</span><span class="p">(</span><span class="s2">&#34;/data/log.txt&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">grep</span><span class="p">(</span><span class="s2">&#34;ERROR&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">wc</span><span class="p">()</span>
</span></span></code></pre></div><p>Now it flows left-to-right. But how do we make arbitrary operations chainable?</p>
<h2 id="the-commandresult-pattern">The CommandResult Pattern</h2>
<p>The key insight is wrapping every result in a chainable container:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">CommandResult</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Wrapper that enables method chaining.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="p">:</span> <span class="n">Any</span>           <span class="c1"># The actual result data</span>
</span></span><span class="line"><span class="cl">    <span class="n">text</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="kc">None</span>    <span class="c1"># Text representation</span>
</span></span><span class="line"><span class="cl">    <span class="n">exit_code</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>  <span class="c1"># Unix-style exit code</span>
</span></span><span class="line"><span class="cl">    <span class="n">_shell</span><span class="p">:</span> <span class="s1">&#39;DagShell&#39;</span> <span class="o">=</span> <span class="kc">None</span>  <span class="c1"># Reference back to the shell</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">text</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">text</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
</span></span></code></pre></div><p>Every command returns a <code>CommandResult</code>. The result carries the data <em>and</em> a reference to the shell that produced it. This reference enables chaining—we can call more methods through it.</p>
<h2 id="making-commands-chainable">Making Commands Chainable</h2>
<p>Here&rsquo;s a simple command implementation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DagShell</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">echo</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">CommandResult</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Echo arguments to output.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">text</span> <span class="o">=</span> <span class="s1">&#39; &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">arg</span><span class="p">)</span> <span class="k">for</span> <span class="n">arg</span> <span class="ow">in</span> <span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">CommandResult</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">data</span><span class="o">=</span><span class="n">text</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">text</span><span class="o">=</span><span class="n">text</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">exit_code</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">_shell</span><span class="o">=</span><span class="bp">self</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span></code></pre></div><p>The magic happens in <code>CommandResult</code>. We add methods that delegate back to the shell:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">CommandResult</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ... fields as before ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">grep</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pattern</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s1">&#39;CommandResult&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Filter lines matching pattern.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_shell</span><span class="o">.</span><span class="n">grep</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">input_data</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">wc</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">flags</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s1">&#39;CommandResult&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Count lines, words, or characters.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_shell</span><span class="o">.</span><span class="n">wc</span><span class="p">(</span><span class="o">*</span><span class="n">flags</span><span class="p">,</span> <span class="n">input_data</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">out</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s1">&#39;CommandResult&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Redirect output to a file.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_shell</span><span class="o">.</span><span class="n">fs</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">encode</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span></code></pre></div><p>Now we can chain:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">shell</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&#34;hello world&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">wc</span><span class="p">(</span><span class="s2">&#34;-w&#34;</span><span class="p">)</span>  <span class="c1"># Returns: 2</span>
</span></span><span class="line"><span class="cl"><span class="n">shell</span><span class="o">.</span><span class="n">cat</span><span class="p">(</span><span class="s2">&#34;/data/log.txt&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">grep</span><span class="p">(</span><span class="s2">&#34;ERROR&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">wc</span><span class="p">(</span><span class="s2">&#34;-l&#34;</span><span class="p">)</span>  <span class="c1"># Count error lines</span>
</span></span></code></pre></div><h2 id="the-dual-nature-objects-and-files">The Dual Nature: Objects and Files</h2>
<p>Unix commands have a superpower: the same output can go to the screen, a file, or another command. We can replicate this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Chain more commands</span>
</span></span><span class="line"><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">shell</span><span class="o">.</span><span class="n">cat</span><span class="p">(</span><span class="s2">&#34;/data/log.txt&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">grep</span><span class="p">(</span><span class="s2">&#34;ERROR&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get as Python object</span>
</span></span><span class="line"><span class="cl"><span class="n">lines</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="n">lines</span><span class="p">()</span>  <span class="c1"># List[str]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Or redirect to a file</span>
</span></span><span class="line"><span class="cl"><span class="n">result</span><span class="o">.</span><span class="n">out</span><span class="p">(</span><span class="s2">&#34;/data/errors.txt&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>The <code>CommandResult</code> is simultaneously:</p>
<ol>
<li>A Python object you can inspect and manipulate</li>
<li>A text stream you can redirect to files</li>
<li>An input source for the next command in a pipeline</li>
</ol>
<p>This dual nature—structured data <em>and</em> text stream—bridges the gap between Unix philosophy and Python&rsquo;s object orientation.</p>
<h2 id="implementing-piping">Implementing Piping</h2>
<p>True Unix pipes pass data between processes. We simulate this with a &ldquo;last result&rdquo; mechanism:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DagShell</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_last_result</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">CommandResult</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">CommandResult</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Return the last command&#39;s result (like $? or $_).&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_last_result</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">CommandResult</span><span class="p">(</span><span class="n">data</span><span class="o">=</span><span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="n">text</span><span class="o">=</span><span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="n">exit_code</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">_shell</span><span class="o">=</span><span class="bp">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_last_result</span>
</span></span></code></pre></div><p>Now we can build pipelines:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">shell</span><span class="o">.</span><span class="n">cat</span><span class="p">(</span><span class="s2">&#34;/data/log.txt&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">shell</span><span class="o">.</span><span class="n">_</span><span class="p">()</span><span class="o">.</span><span class="n">grep</span><span class="p">(</span><span class="s2">&#34;ERROR&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">shell</span><span class="o">.</span><span class="n">_</span><span class="p">()</span><span class="o">.</span><span class="n">wc</span><span class="p">(</span><span class="s2">&#34;-l&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Or more elegantly, commands can accept piped input:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">grep</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pattern</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">input_data</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">CommandResult</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Filter lines matching pattern.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">input_data</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">input_data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_last_result</span><span class="o">.</span><span class="n">data</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_last_result</span> <span class="k">else</span> <span class="s1">&#39;&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">input_data</span><span class="p">)</span><span class="o">.</span><span class="n">splitlines</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">matching</span> <span class="o">=</span> <span class="p">[</span><span class="n">line</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">lines</span> <span class="k">if</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">line</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">CommandResult</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span><span class="o">=</span><span class="n">matching</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">text</span><span class="o">=</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">matching</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">exit_code</span><span class="o">=</span><span class="mi">0</span> <span class="k">if</span> <span class="n">matching</span> <span class="k">else</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">_shell</span><span class="o">=</span><span class="bp">self</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">_last_result</span> <span class="o">=</span> <span class="n">result</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">result</span>
</span></span></code></pre></div><h2 id="method-chaining-in-action">Method Chaining in Action</h2>
<p>Let&rsquo;s build something real—a log analysis pipeline:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Create a shell and some test data</span>
</span></span><span class="line"><span class="cl"><span class="n">shell</span> <span class="o">=</span> <span class="n">DagShell</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">shell</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="s2">&#34;/logs&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">shell</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">2024-01-15 10:30:00 INFO  User login: alice
</span></span></span><span class="line"><span class="cl"><span class="s2">2024-01-15 10:31:00 ERROR Database connection failed
</span></span></span><span class="line"><span class="cl"><span class="s2">2024-01-15 10:32:00 INFO  User login: bob
</span></span></span><span class="line"><span class="cl"><span class="s2">2024-01-15 10:33:00 ERROR Timeout waiting for response
</span></span></span><span class="line"><span class="cl"><span class="s2">2024-01-15 10:34:00 WARN  High memory usage
</span></span></span><span class="line"><span class="cl"><span class="s2">2024-01-15 10:35:00 ERROR Disk space low
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span><span class="o">.</span><span class="n">strip</span><span class="p">())</span><span class="o">.</span><span class="n">out</span><span class="p">(</span><span class="s2">&#34;/logs/app.log&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Pipeline: find errors, extract timestamps, save to file</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="n">shell</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">cat</span><span class="p">(</span><span class="s2">&#34;/logs/app.log&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">grep</span><span class="p">(</span><span class="s2">&#34;ERROR&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">cut</span><span class="p">(</span><span class="n">delimiter</span><span class="o">=</span><span class="s2">&#34; &#34;</span><span class="p">,</span> <span class="n">fields</span><span class="o">=</span><span class="s2">&#34;1,2&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">out</span><span class="p">(</span><span class="s2">&#34;/logs/error_times.txt&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read the result</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">shell</span><span class="o">.</span><span class="n">cat</span><span class="p">(</span><span class="s2">&#34;/logs/error_times.txt&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Output:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 2024-01-15 10:31:00</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 2024-01-15 10:33:00</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 2024-01-15 10:35:00</span>
</span></span></code></pre></div><p>Each step does one thing. The chain composes them into a useful operation. The data flows left-to-right, just like our mental model.</p>
<h2 id="directory-navigation-with-a-stack">Directory Navigation with a Stack</h2>
<p>Unix has <code>cd</code>, but shells also have <code>pushd</code> and <code>popd</code> for directory stacks. We implement this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DagShell</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cwd</span> <span class="o">=</span> <span class="s1">&#39;/&#39;</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_dir_stack</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">pushd</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">CommandResult</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Push current directory and change to new one.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_dir_stack</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_cwd</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">cd</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">popd</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">CommandResult</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Pop directory from stack and change to it.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dir_stack</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">CommandResult</span><span class="p">(</span><span class="n">data</span><span class="o">=</span><span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="n">text</span><span class="o">=</span><span class="s1">&#39;popd: directory stack empty&#39;</span><span class="p">,</span> <span class="n">exit_code</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">old_dir</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dir_stack</span><span class="o">.</span><span class="n">pop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">cd</span><span class="p">(</span><span class="n">old_dir</span><span class="p">)</span>
</span></span></code></pre></div><p>Now we can navigate without losing our place:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">shell</span><span class="o">.</span><span class="n">pushd</span><span class="p">(</span><span class="s2">&#34;/project/src&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ... work in src ...</span>
</span></span><span class="line"><span class="cl"><span class="n">shell</span><span class="o">.</span><span class="n">pushd</span><span class="p">(</span><span class="s2">&#34;tests&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ... work in tests ...</span>
</span></span><span class="line"><span class="cl"><span class="n">shell</span><span class="o">.</span><span class="n">popd</span><span class="p">()</span>  <span class="c1"># back to /project/src</span>
</span></span><span class="line"><span class="cl"><span class="n">shell</span><span class="o">.</span><span class="n">popd</span><span class="p">()</span>  <span class="c1"># back to original directory</span>
</span></span></code></pre></div><h2 id="exit-codes-success-and-failure">Exit Codes: Success and Failure</h2>
<p>Unix commands return exit codes: 0 for success, non-zero for failure. We include this in <code>CommandResult</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">grep</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pattern</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">CommandResult</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ... filtering logic ...</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">CommandResult</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span><span class="o">=</span><span class="n">matching</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">text</span><span class="o">=</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">matching</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">exit_code</span><span class="o">=</span><span class="mi">0</span> <span class="k">if</span> <span class="n">matching</span> <span class="k">else</span> <span class="mi">1</span><span class="p">,</span>  <span class="c1"># 1 if no matches</span>
</span></span><span class="line"><span class="cl">        <span class="n">_shell</span><span class="o">=</span><span class="bp">self</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span></code></pre></div><p>This enables conditional logic:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">shell</span><span class="o">.</span><span class="n">grep</span><span class="p">(</span><span class="s2">&#34;pattern&#34;</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="s2">&#34;/data/log.txt&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">exit_code</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Found </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">lines</span><span class="p">())</span><span class="si">}</span><span class="s2"> matches&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;No matches found&#34;</span><span class="p">)</span>
</span></span></code></pre></div><h2 id="the-philosophy-applied">The Philosophy Applied</h2>
<p>Let&rsquo;s revisit McIlroy&rsquo;s principles and see how we&rsquo;ve applied them:</p>
<p><strong>&ldquo;Do one thing well&rdquo;</strong>: Each method (<code>cat</code>, <code>grep</code>, <code>wc</code>, <code>cut</code>) does exactly one thing.</p>
<p><strong>&ldquo;Work together&rdquo;</strong>: <code>CommandResult</code> enables any command to connect to any other.</p>
<p><strong>&ldquo;Text streams as universal interface&rdquo;</strong>: Every result has a text representation via <code>__str__</code>, making it redirectable and pipeable.</p>
<p>We&rsquo;ve also added Python-specific benefits:</p>
<ul>
<li><strong>Type safety</strong>: Results carry structured <code>data</code>, not just text</li>
<li><strong>Introspection</strong>: <code>result.lines()</code>, <code>result.data</code>, etc.</li>
<li><strong>Chaining</strong>: Method chains read left-to-right like pipelines</li>
</ul>
<h2 id="the-fluent-pattern">The Fluent Pattern</h2>
<p>This is an instance of the <strong>Fluent Interface</strong> pattern, where methods return <code>self</code> (or a related object) to enable chaining. Martin Fowler described it in 2005, but the idea is older—Smalltalk embraced it from the beginning.</p>
<p>The key is designing methods that:</p>
<ol>
<li>Perform their action</li>
<li>Return something chainable</li>
<li>Maintain enough context for the next operation</li>
</ol>
<p>When done well, code reads almost like prose:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="p">(</span><span class="n">shell</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="s2">&#34;/project&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">cd</span><span class="p">(</span><span class="s2">&#34;/project&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&#34;# My Project&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">out</span><span class="p">(</span><span class="s2">&#34;README.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&#34;def main(): pass&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">out</span><span class="p">(</span><span class="s2">&#34;main.py&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">ls</span><span class="p">(</span><span class="s2">&#34;-la&#34;</span><span class="p">))</span>
</span></span></code></pre></div><h2 id="trade-offs">Trade-offs</h2>
<p>This approach isn&rsquo;t free:</p>
<ol>
<li><strong>Wrapper overhead</strong>: Every result is wrapped in <code>CommandResult</code></li>
<li><strong>Learning curve</strong>: Users must understand the chaining pattern</li>
<li><strong>Debugging</strong>: Long chains can be hard to debug—where did it fail?</li>
</ol>
<p>For scripting and exploratory work, the expressiveness outweighs these costs. For performance-critical code, you might unwrap to raw operations.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The Unix philosophy isn&rsquo;t about Unix—it&rsquo;s about composability. Small, focused operations that connect through a universal interface create systems greater than the sum of their parts.</p>
<p>In Python, we achieve this with:</p>
<ul>
<li><strong>Wrapper types</strong> that carry data and context</li>
<li><strong>Method chaining</strong> for left-to-right flow</li>
<li><strong>Dual representations</strong> as objects and text streams</li>
<li><strong>Exit codes</strong> for success/failure signaling</li>
</ul>
<p>The result is a fluent interface where complex operations emerge from simple, composable parts—Unix philosophy, realized in Python.</p>
<hr>
<p><em>See the full implementation in <a href="https://github.com/queelius/dagshell">DagShell</a>, which builds a complete virtual filesystem with this fluent pattern.</em></p>
<p><em>Next in this series: <a href="../03-embedding-scheme-interpreter/">Embedding a Scheme Interpreter</a> — adding a DSL for filesystem scripting.</em></p>
]]></content:encoded></item><item><title>Embedding a Scheme Interpreter: Building a DSL for Filesystem Operations</title><link>https://metafunctor.com/projects/dagshell/posts/03-embedding-scheme-interpreter/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/projects/dagshell/posts/03-embedding-scheme-interpreter/</guid><description>How to implement a minimal Scheme and integrate it with your application</description><content:encoded><![CDATA[<p>Every sufficiently complex application eventually grows a scripting language. Emacs has Elisp. AutoCAD has AutoLISP. Blender has Python. The pattern is clear: users need programmability beyond what a fixed UI provides.</p>
<p>But embedding a full language runtime is heavy. What if we just need something small—variables, functions, conditionals, loops? A Lisp-family language is perfect for this: the syntax is trivial to parse, the semantics are clean, and it&rsquo;s powerful enough for real scripting.</p>
<p>In this post, I&rsquo;ll walk through building a minimal Scheme interpreter in Python and integrating it with a virtual filesystem. We&rsquo;ll cover the complete pipeline: tokenization, parsing, evaluation, and extension with custom primitives.</p>
<h2 id="why-scheme">Why Scheme?</h2>
<p>Scheme is a minimalist Lisp dialect. Its syntax is just parentheses and atoms:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-scheme" data-lang="scheme"><span class="line"><span class="cl"><span class="p">(</span><span class="k">define </span><span class="nv">x</span> <span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">+ </span><span class="nv">x</span> <span class="p">(</span><span class="nb">* </span><span class="mi">2</span> <span class="mi">3</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">&gt; </span><span class="nv">x</span> <span class="mi">5</span><span class="p">)</span> <span class="s">&#34;big&#34;</span> <span class="s">&#34;small&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>This uniformity makes parsing trivial. There&rsquo;s no operator precedence, no complex grammar—just:</p>
<ol>
<li>Atoms: numbers, strings, symbols</li>
<li>Lists: <code>(thing thing thing ...)</code></li>
</ol>
<p>That&rsquo;s it. The entire parser can be written in under 50 lines.</p>
<h2 id="step-1-tokenization">Step 1: Tokenization</h2>
<p>First, we convert source code into tokens. Scheme&rsquo;s tokenization is simple: split on whitespace, but respect strings and parentheses.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">tokenize</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Convert Scheme code into tokens.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Handle comments (lines starting with ;)</span>
</span></span><span class="line"><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">text</span> <span class="o">=</span> <span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">line</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;;&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Add spaces around parens for easy splitting</span>
</span></span><span class="line"><span class="cl">    <span class="n">text</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;(&#39;</span><span class="p">,</span> <span class="s1">&#39; ( &#39;</span><span class="p">)</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;)&#39;</span><span class="p">,</span> <span class="s1">&#39; ) &#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Split, handling string literals</span>
</span></span><span class="line"><span class="cl">    <span class="n">tokens</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="n">in_string</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="cl">    <span class="n">current</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">char</span> <span class="ow">in</span> <span class="n">text</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">char</span> <span class="o">==</span> <span class="s1">&#39;&#34;&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">in_string</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_string</span>
</span></span><span class="line"><span class="cl">            <span class="n">current</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">in_string</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">current</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">char</span><span class="o">.</span><span class="n">isspace</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">current</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">tokens</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">current</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="n">current</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">current</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">current</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">tokens</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">current</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">tokens</span>
</span></span></code></pre></div><p>Input: <code>(define x (+ 1 2))</code>
Output: <code>['(', 'define', 'x', '(', '+', '1', '2', ')', ')']</code></p>
<h2 id="step-2-parsing">Step 2: Parsing</h2>
<p>Parsing converts tokens into an Abstract Syntax Tree (AST). In Scheme, the AST is just nested Python lists:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Symbol</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Represents a Scheme symbol.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">parse</span><span class="p">(</span><span class="n">tokens</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Parse tokens into an AST.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">parse_expr</span><span class="p">(</span><span class="n">index</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Tuple</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="nb">int</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="n">token</span> <span class="o">=</span> <span class="n">tokens</span><span class="p">[</span><span class="n">index</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">token</span> <span class="o">==</span> <span class="s1">&#39;(&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Parse a list</span>
</span></span><span class="line"><span class="cl">            <span class="n">lst</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">            <span class="n">index</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">            <span class="k">while</span> <span class="n">tokens</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="o">!=</span> <span class="s1">&#39;)&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">expr</span><span class="p">,</span> <span class="n">index</span> <span class="o">=</span> <span class="n">parse_expr</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">lst</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">expr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">lst</span><span class="p">,</span> <span class="n">index</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">token</span> <span class="o">==</span> <span class="s1">&#39;)&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="s2">&#34;Unexpected )&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Parse an atom</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">parse_atom</span><span class="p">(</span><span class="n">token</span><span class="p">),</span> <span class="n">index</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">expr</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">parse_expr</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">expr</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">parse_atom</span><span class="p">(</span><span class="n">token</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Parse a single atom.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Try integer</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Try float</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">float</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># String literal</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">token</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">&#39;&#34;&#39;</span><span class="p">)</span> <span class="ow">and</span> <span class="n">token</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s1">&#39;&#34;&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">token</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Boolean</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">token</span> <span class="o">==</span> <span class="s1">&#39;#t&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">token</span> <span class="o">==</span> <span class="s1">&#39;#f&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Symbol</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">Symbol</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
</span></span></code></pre></div><p>Input tokens: <code>['(', 'define', 'x', '(', '+', '1', '2', ')', ')']</code>
Output AST: <code>[Symbol('define'), Symbol('x'), [Symbol('+'), 1, 2]]</code></p>
<p>The beauty of Lisp: the AST <em>is</em> the syntax. There&rsquo;s no separate tree structure—it&rsquo;s just lists of symbols and values.</p>
<h2 id="step-3-the-environment">Step 3: The Environment</h2>
<p>Before evaluation, we need an environment to track variable bindings. This is where lexical scoping lives:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Environment</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Lexical environment for variable bindings.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">parent</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="s1">&#39;Environment&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">bindings</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">parent</span> <span class="o">=</span> <span class="n">parent</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">define</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Define a new binding in this environment.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">bindings</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Look up a binding, checking parent scopes.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">name</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">bindings</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">bindings</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">parent</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span> <span class="ne">NameError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Undefined variable: </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">set</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Update an existing binding.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">name</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">bindings</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">bindings</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">parent</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span> <span class="ne">NameError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Undefined variable: </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Each function call creates a new Environment with the current one as its parent. This chain enables closures—inner functions that remember their enclosing scope.</p>
<h2 id="step-4-evaluation">Step 4: Evaluation</h2>
<p>The evaluator is the heart of the interpreter. It walks the AST and computes values:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">evaluate</span><span class="p">(</span><span class="n">expr</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">env</span><span class="p">:</span> <span class="n">Environment</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Evaluate an expression in an environment.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Self-evaluating: numbers, strings, booleans</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">expr</span><span class="p">,</span> <span class="p">(</span><span class="nb">int</span><span class="p">,</span> <span class="nb">float</span><span class="p">,</span> <span class="nb">str</span><span class="p">,</span> <span class="nb">bool</span><span class="p">,</span> <span class="nb">type</span><span class="p">(</span><span class="kc">None</span><span class="p">))):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">expr</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Variable lookup</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">expr</span><span class="p">,</span> <span class="n">Symbol</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">env</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">expr</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Must be a list (function call or special form)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">expr</span><span class="p">,</span> <span class="nb">list</span><span class="p">)</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">expr</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">expr</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">op</span> <span class="o">=</span> <span class="n">expr</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Special forms</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="n">Symbol</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s1">&#39;quote&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">expr</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s1">&#39;define&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">name</span> <span class="o">=</span> <span class="n">expr</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">expr</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="n">env</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="n">name</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">value</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s1">&#39;if&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">condition</span> <span class="o">=</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">expr</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">env</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">condition</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">expr</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="n">env</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">elif</span> <span class="nb">len</span><span class="p">(</span><span class="n">expr</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">3</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">expr</span><span class="p">[</span><span class="mi">3</span><span class="p">],</span> <span class="n">env</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s1">&#39;lambda&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">params</span> <span class="o">=</span> <span class="n">expr</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="n">body</span> <span class="o">=</span> <span class="n">expr</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">Procedure</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">body</span><span class="p">,</span> <span class="n">env</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Function application</span>
</span></span><span class="line"><span class="cl">    <span class="n">func</span> <span class="o">=</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="n">env</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">args</span> <span class="o">=</span> <span class="p">[</span><span class="n">evaluate</span><span class="p">(</span><span class="n">arg</span><span class="p">,</span> <span class="n">env</span><span class="p">)</span> <span class="k">for</span> <span class="n">arg</span> <span class="ow">in</span> <span class="n">expr</span><span class="p">[</span><span class="mi">1</span><span class="p">:]]</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
</span></span></code></pre></div><p>Special forms (<code>define</code>, <code>if</code>, <code>lambda</code>, etc.) have custom evaluation rules. Everything else is a function call: evaluate the operator, evaluate the arguments, call the function.</p>
<h2 id="step-5-user-defined-functions">Step 5: User-Defined Functions</h2>
<p>The <code>lambda</code> form creates procedures. A Procedure captures its parameters, body, and defining environment:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Procedure</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;A user-defined function.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">params</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Symbol</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">body</span><span class="p">:</span> <span class="n">Any</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="p">:</span> <span class="n">Environment</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Create a new environment for the call</span>
</span></span><span class="line"><span class="cl">        <span class="n">local_env</span> <span class="o">=</span> <span class="n">Environment</span><span class="p">(</span><span class="n">parent</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Bind parameters to arguments</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">param</span><span class="p">,</span> <span class="n">arg</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">params</span><span class="p">,</span> <span class="n">args</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="n">local_env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="n">param</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">arg</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Evaluate the body in this new environment</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">evaluate</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">body</span><span class="p">,</span> <span class="n">local_env</span><span class="p">)</span>
</span></span></code></pre></div><p>When called, a Procedure:</p>
<ol>
<li>Creates a new environment with its defining environment as parent (closure!)</li>
<li>Binds parameters to the passed arguments</li>
<li>Evaluates its body in this new environment</li>
</ol>
<p>This simple structure gives us closures, higher-order functions, and lexical scoping.</p>
<h2 id="step-6-built-in-primitives">Step 6: Built-in Primitives</h2>
<p>The global environment provides built-in functions:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">create_global_env</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">Environment</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Create environment with built-in primitives.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span> <span class="o">=</span> <span class="n">Environment</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Arithmetic</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;+&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="nb">sum</span><span class="p">(</span><span class="n">args</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="o">=</span><span class="kc">None</span><span class="p">:</span> <span class="o">-</span><span class="n">a</span> <span class="k">if</span> <span class="n">b</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">a</span> <span class="o">-</span> <span class="n">b</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;*&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">reduce</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="n">x</span> <span class="o">*</span> <span class="n">y</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="n">a</span> <span class="o">/</span> <span class="n">b</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Comparison</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;=&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="n">a</span> <span class="o">==</span> <span class="n">b</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;&lt;&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="n">a</span> <span class="o">&lt;</span> <span class="n">b</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;&gt;&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="n">a</span> <span class="o">&gt;</span> <span class="n">b</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># List operations</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;car&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">lst</span><span class="p">:</span> <span class="n">lst</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;cdr&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">lst</span><span class="p">:</span> <span class="n">lst</span><span class="p">[</span><span class="mi">1</span><span class="p">:])</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;cons&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="p">[</span><span class="n">a</span><span class="p">]</span> <span class="o">+</span> <span class="nb">list</span><span class="p">(</span><span class="n">b</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;list&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="nb">list</span><span class="p">(</span><span class="n">args</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;null?&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">lst</span><span class="p">:</span> <span class="n">lst</span> <span class="o">==</span> <span class="p">[])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Higher-order functions</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;map&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">f</span><span class="p">,</span> <span class="n">lst</span><span class="p">:</span> <span class="p">[</span><span class="n">f</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">lst</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;filter&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">f</span><span class="p">,</span> <span class="n">lst</span><span class="p">:</span> <span class="p">[</span><span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">lst</span> <span class="k">if</span> <span class="n">f</span><span class="p">(</span><span class="n">x</span><span class="p">)])</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;reduce&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">f</span><span class="p">,</span> <span class="n">lst</span><span class="p">,</span> <span class="n">init</span><span class="p">:</span> <span class="n">reduce</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">lst</span><span class="p">,</span> <span class="n">init</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">env</span>
</span></span></code></pre></div><p>Each primitive is just a Python function. The interpreter treats them identically to user-defined procedures.</p>
<h2 id="step-7-filesystem-integration">Step 7: Filesystem Integration</h2>
<p>Now the interesting part: extending Scheme with filesystem primitives.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">create_global_env</span><span class="p">(</span><span class="n">shell</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Environment</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">env</span> <span class="o">=</span> <span class="n">Environment</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># ... standard primitives ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Filesystem primitives (if shell provided)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">shell</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;ls&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">path</span><span class="o">=</span><span class="s1">&#39;/&#39;</span><span class="p">:</span> <span class="n">shell</span><span class="o">.</span><span class="n">ls</span><span class="p">(</span><span class="n">path</span><span class="p">)</span><span class="o">.</span><span class="n">lines</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;cat&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">path</span><span class="p">:</span> <span class="n">shell</span><span class="o">.</span><span class="n">cat</span><span class="p">(</span><span class="n">path</span><span class="p">)</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;mkdir&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">path</span><span class="p">:</span> <span class="n">shell</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="ow">and</span> <span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;write&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">path</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">shell</span><span class="o">.</span><span class="n">fs</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">content</span><span class="o">.</span><span class="n">encode</span><span class="p">())</span> <span class="ow">and</span> <span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;exists?&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">path</span><span class="p">:</span> <span class="n">shell</span><span class="o">.</span><span class="n">fs</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">path</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;cd&#39;</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">path</span><span class="p">:</span> <span class="n">shell</span><span class="o">.</span><span class="n">cd</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="ow">and</span> <span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">env</span><span class="o">.</span><span class="n">define</span><span class="p">(</span><span class="s1">&#39;pwd&#39;</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">shell</span><span class="o">.</span><span class="n">_cwd</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">env</span>
</span></span></code></pre></div><p>Now Scheme can script filesystem operations:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-scheme" data-lang="scheme"><span class="line"><span class="cl"><span class="c1">; Create a project structure</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nf">mkdir</span> <span class="s">&#34;/project&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nf">mkdir</span> <span class="s">&#34;/project/src&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nf">mkdir</span> <span class="s">&#34;/project/tests&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">write </span><span class="s">&#34;/project/README.md&#34;</span> <span class="s">&#34;# My Project\n&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">write </span><span class="s">&#34;/project/src/main.py&#34;</span> <span class="s">&#34;def main(): pass\n&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">; List files</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nf">ls</span> <span class="s">&#34;/project&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">; =&gt; (&#34;README.md&#34; &#34;src&#34; &#34;tests&#34;)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">; Check existence</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nf">exists?</span> <span class="s">&#34;/project/README.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Found it!&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Not found&#34;</span><span class="p">)</span>
</span></span></code></pre></div><h2 id="step-8-the-repl">Step 8: The REPL</h2>
<p>A Read-Eval-Print Loop ties it together:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">SchemeREPL</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">shell</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">env</span> <span class="o">=</span> <span class="n">create_global_env</span><span class="p">(</span><span class="n">shell</span><span class="o">=</span><span class="n">shell</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">eval_string</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">code</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Evaluate a string of Scheme code.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">tokens</span> <span class="o">=</span> <span class="n">tokenize</span><span class="p">(</span><span class="n">code</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">tokens</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">        <span class="n">idx</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">while</span> <span class="n">idx</span> <span class="o">&lt;</span> <span class="nb">len</span><span class="p">(</span><span class="n">tokens</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="n">expr</span><span class="p">,</span> <span class="n">idx</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_parse_one</span><span class="p">(</span><span class="n">tokens</span><span class="p">,</span> <span class="n">idx</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">expr</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Interactive REPL.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Scheme REPL (type &#39;quit&#39; to exit)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">code</span> <span class="o">=</span> <span class="nb">input</span><span class="p">(</span><span class="s2">&#34;scheme&gt; &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="n">code</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="o">==</span> <span class="s1">&#39;quit&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="k">break</span>
</span></span><span class="line"><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">eval_string</span><span class="p">(</span><span class="n">code</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="n">result</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">print</span><span class="p">(</span><span class="n">format_value</span><span class="p">(</span><span class="n">result</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Error: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span></code></pre></div><h2 id="example-session">Example Session</h2>
<p>Here&rsquo;s what a session looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">scheme&gt; (define double (lambda (x) (* x 2)))
</span></span><span class="line"><span class="cl">scheme&gt; (double 21)
</span></span><span class="line"><span class="cl">42
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">scheme&gt; (map double (list 1 2 3 4 5))
</span></span><span class="line"><span class="cl">(2 4 6 8 10)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">scheme&gt; (mkdir &#34;/data&#34;)
</span></span><span class="line"><span class="cl">&#34;/data&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">scheme&gt; (write &#34;/data/numbers.txt&#34; &#34;1\n2\n3\n&#34;)
</span></span><span class="line"><span class="cl">&#34;/data/numbers.txt&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">scheme&gt; (cat &#34;/data/numbers.txt&#34;)
</span></span><span class="line"><span class="cl">&#34;1\n2\n3\n&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">scheme&gt; (filter (lambda (x) (&gt; x 1))
</span></span><span class="line"><span class="cl">               (map (lambda (s) (string-&gt;number s))
</span></span><span class="line"><span class="cl">                    (string-split (cat &#34;/data/numbers.txt&#34;) &#34;\n&#34;)))
</span></span><span class="line"><span class="cl">(2 3)
</span></span></code></pre></div><p>We&rsquo;ve built a scriptable filesystem with a dozen lines of primitives!</p>
<h2 id="design-patterns">Design Patterns</h2>
<p>Several patterns make this work:</p>
<p><strong>Homoiconicity</strong>: Code is data. The AST is just lists, so we can generate and manipulate code programmatically.</p>
<p><strong>Closures</strong>: Functions capture their environment, enabling powerful patterns like partial application and callbacks.</p>
<p><strong>Extension via primitives</strong>: Adding features is just adding functions to the global environment. No interpreter changes needed.</p>
<p><strong>Separation of concerns</strong>: Tokenizing, parsing, and evaluating are distinct phases with clean interfaces.</p>
<h2 id="trade-offs">Trade-offs</h2>
<p>This simple interpreter has limitations:</p>
<ol>
<li><strong>No tail-call optimization</strong>: Deep recursion will overflow the Python stack.</li>
<li><strong>No macros</strong>: We can&rsquo;t extend the language&rsquo;s syntax.</li>
<li><strong>Error messages are basic</strong>: Line numbers would require more tracking.</li>
<li><strong>Performance</strong>: Pure interpretation is slow for heavy computation.</li>
</ol>
<p>For a DSL extending an application, these trade-offs are often acceptable. The simplicity enables understanding and modification.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Embedding a language isn&rsquo;t black magic. At its core:</p>
<ol>
<li><strong>Tokenize</strong>: Break text into tokens</li>
<li><strong>Parse</strong>: Build an AST (for Lisp, just nested lists)</li>
<li><strong>Evaluate</strong>: Walk the AST, computing values</li>
<li><strong>Extend</strong>: Add primitives that call into your application</li>
</ol>
<p>Scheme&rsquo;s minimal syntax makes this especially tractable. In a few hundred lines, we get variables, functions, closures, conditionals, recursion, and extensibility.</p>
<p>The result is a scriptable application where users can automate, experiment, and build beyond what the core interface provides—exactly what we want from a DSL.</p>
<hr>
<p><em>The complete implementation is in <a href="https://github.com/queelius/dagshell">DagShell</a>, demonstrating Scheme as a filesystem scripting language alongside Python&rsquo;s fluent API.</em></p>
<p><em>This is the final post in the series. Previous posts: <a href="../01-immutable-content-addressed-filesystem/">Immutable Content-Addressed Filesystems</a> and <a href="../02-unix-philosophy-python/">Unix Philosophy in Python</a>.</em></p>
]]></content:encoded></item><item><title>Watch: Calculus, The Big Ideas</title><link>https://metafunctor.com/post/2026-07-09-calculus-playlist/</link><pubDate>Thu, 09 Jul 2026 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/post/2026-07-09-calculus-playlist/</guid><description>Calculus as one idea examined from every side: a seven-episode animated playlist running from limits through the derivative, the integral, the fundamental theorem, gradients, and optimization.</description><content:encoded><![CDATA[<p>Most of calculus is one idea, held up to the light and turned slowly. This is a seven-episode animated playlist that does exactly that: it starts from <em>approaching</em> and never really leaves it.</p>
<p>The idea is the limit. Watch a run of guesses close in on a value they may never quite land on, and you have the whole engine. Everything after is that same move, seen from a new angle. The <strong>derivative</strong> is a limit: shrink an interval until a secant line becomes a tangent, and its slope is an instantaneous rate of change, the amount a function stretches a tiny nudge as it passes through a point. The <strong>chain rule</strong> is what happens when those stretch factors are stacked in stages: the rates multiply, like meshed gears. The <strong>integral</strong> runs the whole thing backward, adding up a rate over its domain to recover a total, an area built from infinitely many infinitely thin slices. The <strong>fundamental theorem</strong> then closes the loop and shows that the derivative and the integral were two views of one object all along: differentiate an accumulation and the original rate comes straight back.</p>
<p>The last two episodes let the world have more than one direction. In <strong>many dimensions</strong> the slope grows into the gradient, a vector pointing straight uphill, and the second derivative becomes curvature; a peak is simply where the uphill direction vanishes. <strong>Optimization</strong> is the art of getting there when you cannot solve for that point directly: step along the gradient, over and over, or use the curvature to leap. That last pair is the ground under a great deal of applied mathematics, from fitting a model to its data to training a neural network.</p>
<p>Each episode is narrated and animated, math spoken as plain English rather than read off as symbols, and each one is deliberately load-bearing for the series that follow. If you have ever felt that calculus was a pile of unrelated rules, this is the argument that it is one subject, built from one idea.</p>
<h2 id="the-playlist">The playlist</h2>
<div class="youtube-series">
  <div class="youtube-playlist" style="position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden;margin:1.5rem 0;">
    <iframe
      src="https://www.youtube-nocookie.com/embed/videoseries?list=PLM9CL2Ufh7L8"
      title="Calculus: The Big Ideas"
      style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
      allowfullscreen
      loading="lazy"
      referrerpolicy="strict-origin-when-cross-origin"></iframe>
  </div>
  <p><a href="https://www.youtube.com/playlist?list=PLM9CL2Ufh7L8" rel="noopener" target="_blank">Open the playlist on YouTube</a> (7 episodes)</p>
  <details class="youtube-series-episodes">
    <summary>Episode list</summary>
    <ol>
      <li><a href="https://youtu.be/bDnznF-0AfI" rel="noopener" target="_blank">Limits: Making &#39;Approaching&#39; Precise</a></li>
      <li><a href="https://youtu.be/VIJ-P-Mf240" rel="noopener" target="_blank">The Derivative: Rate of Change</a></li>
      <li><a href="https://youtu.be/Hoeip-n1Krs" rel="noopener" target="_blank">The Chain Rule: Rates That Nest</a></li>
      <li><a href="https://youtu.be/jpmX5ypmmeU" rel="noopener" target="_blank">The Integral: Accumulation</a></li>
      <li><a href="https://youtu.be/cwCvg_xCacg" rel="noopener" target="_blank">The Fundamental Theorem: The Two Halves Meet</a></li>
      <li><a href="https://youtu.be/wbT-GXmk30I" rel="noopener" target="_blank">Calculus in Many Dimensions: Gradients and Peaks</a></li>
      <li><a href="https://youtu.be/LJyHiN_lnfw" rel="noopener" target="_blank">Optimization: Walking to the Top</a></li>
    </ol>
  </details>
</div>
]]></content:encoded><media:content url="https://metafunctor.com/post/2026-07-09-calculus-playlist/card.jpg" medium="image"><media:title type="plain"/></media:content><category>youtube</category><category>video</category><category>calculus</category><category>mathematics</category><category>animation</category></item><item><title>The Channel: Animated Series on YouTube</title><link>https://metafunctor.com/post/2026-07-04-youtube-channel/</link><pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/post/2026-07-04-youtube-channel/</guid><description>The metafunctor YouTube channel now carries animated versions of the site's series, built from the same source material with a from-scratch pipeline. Four playlists so far, thirty-seven videos.</description><content:encoded><![CDATA[<p>For a while now the writing here has come in series: a set of posts that build one idea from the ground up, in order, until the whole thing stands. That format wants to be watched as much as read. A diagram that assembles itself, a value that moves through a pipeline, a bar chart of amplitudes that shifts when you apply a gate: these are easier to follow when they move. So I built a way to make them move, and the results now live on the metafunctor YouTube channel.</p>
<p>The videos are not a separate project. Each one is built from the same source material as the post it comes from, run through a from-scratch pipeline: Manim for the animation, narrated dialogue for the voice, and the code and math taken straight from the series text. A series landing page here embeds its full playlist, and each individual post embeds its own episode at the bottom, so you can read and watch in the same place without hunting for anything.</p>
<p>There are four playlists so far, thirty-seven videos in total. Here they are.</p>
<h2 id="algorithms-arise-from-algebraic-structure">Algorithms Arise from Algebraic Structure</h2>
<p>The Stepanov series, nine episodes: declare the algebraic structure a problem lives in, and the algorithms fall out for free.</p>
<div class="youtube-series">
  <div class="youtube-playlist" style="position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden;margin:1.5rem 0;">
    <iframe
      src="https://www.youtube-nocookie.com/embed/videoseries?list=PLDqx3XgZpNtc"
      title="Algorithms Arise from Algebraic Structure"
      style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
      allowfullscreen
      loading="lazy"
      referrerpolicy="strict-origin-when-cross-origin"></iframe>
  </div>
  <p><a href="https://www.youtube.com/playlist?list=PLDqx3XgZpNtc" rel="noopener" target="_blank">Open the playlist on YouTube</a> (9 episodes)</p>
  <details class="youtube-series-episodes">
    <summary>Episode list</summary>
    <ol>
      <li><a href="https://youtu.be/7vqIJUslg98" rel="noopener" target="_blank">Algorithms Arise from Algebraic Structure</a></li>
      <li><a href="https://youtu.be/VnRQvcCeF4g" rel="noopener" target="_blank">One Algorithm, Infinite Powers</a></li>
      <li><a href="https://youtu.be/NBZdDibD4n8" rel="noopener" target="_blank">Polynomials as Euclidean Domains</a></li>
      <li><a href="https://youtu.be/rK_dFGbpfDk" rel="noopener" target="_blank">Forward-Mode Automatic Differentiation via Dual Numbers</a></li>
      <li><a href="https://youtu.be/Cl-OvMNmlco" rel="noopener" target="_blank">Reverse-Mode Automatic Differentiation</a></li>
      <li><a href="https://youtu.be/Ha1i2XeFE8M" rel="noopener" target="_blank">Semirings: One Algorithm, Six Graph Problems</a></li>
      <li><a href="https://youtu.be/XveBExQ5XO8" rel="noopener" target="_blank">Homomorphisms: Fold Is the Universal Map</a></li>
      <li><a href="https://youtu.be/Y76txQRQssY" rel="noopener" target="_blank">Free Algebras: Why Lists and Polynomials Are Universal</a></li>
      <li><a href="https://youtu.be/SYIE_zzVMp8" rel="noopener" target="_blank">Lattices: Fixed Points and Tarski&#39;s Theorem</a></li>
    </ol>
  </details>
</div>
<h2 id="next-token-prediction-from-solomonoff-to-transformers">Next-Token Prediction: From Solomonoff to Transformers</h2>
<p>The sequential-prediction series, eight episodes: what-comes-next as the foundation the whole language-model era stands on, traced from Solomonoff to the transformer.</p>
<div class="youtube-series">
  <div class="youtube-playlist" style="position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden;margin:1.5rem 0;">
    <iframe
      src="https://www.youtube-nocookie.com/embed/videoseries?list=PLdOm3n4SXt1s"
      title="Next-Token Prediction: From Solomonoff to Transformers"
      style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
      allowfullscreen
      loading="lazy"
      referrerpolicy="strict-origin-when-cross-origin"></iframe>
  </div>
  <p><a href="https://www.youtube.com/playlist?list=PLdOm3n4SXt1s" rel="noopener" target="_blank">Open the playlist on YouTube</a> (8 episodes)</p>
  <details class="youtube-series-episodes">
    <summary>Episode list</summary>
    <ol>
      <li><a href="https://youtu.be/Yp4LqvjIK2U" rel="noopener" target="_blank">Why Predict the Next Symbol?</a></li>
      <li><a href="https://youtu.be/iJj0LkCwp_c" rel="noopener" target="_blank">Introduction to Sequential Prediction</a></li>
      <li><a href="https://youtu.be/Z7zY2S2xNVo" rel="noopener" target="_blank">Solomonoff Induction: The Incomputable Ideal</a></li>
      <li><a href="https://youtu.be/JWF67iKURY8" rel="noopener" target="_blank">The Bayesian Prediction Framework</a></li>
      <li><a href="https://youtu.be/1xoUtn4t2K4" rel="noopener" target="_blank">N-gram Language Models: Counting and Smoothing</a></li>
      <li><a href="https://youtu.be/VT-2XCIZ5qo" rel="noopener" target="_blank">Context Tree Weighting: Theory Meets Practice</a></li>
      <li><a href="https://youtu.be/DfWX30itQxM" rel="noopener" target="_blank">Neural Language Models: From RNNs to Transformers</a></li>
      <li><a href="https://youtu.be/oRZMoPTfc_I" rel="noopener" target="_blank">CTW vs. N-grams vs. Neural Language Models</a></li>
    </ol>
  </details>
</div>
<h2 id="quantum-computing-from-scratch-in-python">Quantum Computing from Scratch, in Python</h2>
<p>The quantum series, ten episodes: a working simulator built in NumPy from nothing, with a visual language of amplitude bars that makes interference something you can see.</p>
<div class="youtube-series">
  <div class="youtube-playlist" style="position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden;margin:1.5rem 0;">
    <iframe
      src="https://www.youtube-nocookie.com/embed/videoseries?list=PLdnVusLuULrk"
      title="Quantum Computing from Scratch, in Python"
      style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
      allowfullscreen
      loading="lazy"
      referrerpolicy="strict-origin-when-cross-origin"></iframe>
  </div>
  <p><a href="https://www.youtube.com/playlist?list=PLdnVusLuULrk" rel="noopener" target="_blank">Open the playlist on YouTube</a> (10 episodes)</p>
  <details class="youtube-series-episodes">
    <summary>Episode list</summary>
    <ol>
      <li><a href="https://youtu.be/Qr_Usifr-hA" rel="noopener" target="_blank">Why Build a Quantum Computer from Scratch?</a></li>
      <li><a href="https://youtu.be/kSbtARgBRw8" rel="noopener" target="_blank">Classical Information First: The Coin Under the Cup</a></li>
      <li><a href="https://youtu.be/eJmBXYV2OWw" rel="noopener" target="_blank">What Is a Qubit? Building the First Gate</a></li>
      <li><a href="https://youtu.be/ozAh5eZ_vzU" rel="noopener" target="_blank">Many Qubits and Entanglement: Tensor Products</a></li>
      <li><a href="https://youtu.be/9p9FKV3qIOI" rel="noopener" target="_blank">Circuits and Interference: Making Amplitudes Useful</a></li>
      <li><a href="https://youtu.be/wYbiEoXGzbI" rel="noopener" target="_blank">Grover&#39;s Search: Quadratic Speedup from Structured Interference</a></li>
      <li><a href="https://youtu.be/x7oO9lIXOgU" rel="noopener" target="_blank">The Quantum Fourier Transform</a></li>
      <li><a href="https://youtu.be/_zxKyE3OGU0" rel="noopener" target="_blank">Shor&#39;s Algorithm: Why RSA Has an Expiration Date</a></li>
      <li><a href="https://youtu.be/bReKWSgdVIw" rel="noopener" target="_blank">Mixed States and the Density Matrix</a></li>
      <li><a href="https://youtu.be/MBYeyYJSDQM" rel="noopener" target="_blank">Noise, Decoherence, and Why Error Correction Is Hard</a></li>
    </ol>
  </details>
</div>
<h2 id="inductive-biases-what-your-architecture-assumes">Inductive Biases: What Your Architecture Assumes</h2>
<p>The inductive-biases series, ten episodes: every architecture is a bet on the shape of the world, and each one is graded on the same scorecard, what it assumes, what that assumption buys, and what it costs when the world does not comply.</p>
<div class="youtube-series">
  <div class="youtube-playlist" style="position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden;margin:1.5rem 0;">
    <iframe
      src="https://www.youtube-nocookie.com/embed/videoseries?list=PLQTcXcKfBblU"
      title="Inductive Biases: What Your Architecture Assumes"
      style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
      allowfullscreen
      loading="lazy"
      referrerpolicy="strict-origin-when-cross-origin"></iframe>
  </div>
  <p><a href="https://www.youtube.com/playlist?list=PLQTcXcKfBblU" rel="noopener" target="_blank">Open the playlist on YouTube</a> (10 episodes)</p>
  <details class="youtube-series-episodes">
    <summary>Episode list</summary>
    <ol>
      <li><a href="https://youtu.be/n47TpVeXHFU" rel="noopener" target="_blank">Learning Without Assumptions Is Impossible</a></li>
      <li><a href="https://youtu.be/KLufDBw4um0" rel="noopener" target="_blank">Sample Efficiency and the Price of Priors</a></li>
      <li><a href="https://youtu.be/d01x3xo3HF8" rel="noopener" target="_blank">A Network Computes Numbers. The Loss Decides What They Mean.</a></li>
      <li><a href="https://youtu.be/OamSnH-Do6M" rel="noopener" target="_blank">The Loss Function Is a Distribution Assumption</a></li>
      <li><a href="https://youtu.be/4FrVL9lOPh8" rel="noopener" target="_blank">What a Convolution Assumes</a></li>
      <li><a href="https://youtu.be/CsLhbiC-4Co" rel="noopener" target="_blank">Bengio&#39;s Language Model: The Markov Assumption Made Architectural</a></li>
      <li><a href="https://youtu.be/d9Adp6KktOI" rel="noopener" target="_blank">Recurrence Is Weight Sharing Across Time, and It Costs You</a></li>
      <li><a href="https://youtu.be/AFZJW5RdckI" rel="noopener" target="_blank">Attention Is a Learned Pointer Dereference</a></li>
      <li><a href="https://youtu.be/GiJwuZ66H3s" rel="noopener" target="_blank">Attention Weight Is Not Information Flow</a></li>
      <li><a href="https://youtu.be/DJNBGPVek8M" rel="noopener" target="_blank">Reinforcement Learning Is Cross-Entropy, Reweighted by Reward</a></li>
    </ol>
  </details>
</div>
<p>More playlists are coming, drawn from the other series on the site as I get the animation for each one right. If a series here has earned a place in your reading, it now has a version you can watch too. The plan is to keep the two in step: what gets written gets animated, and both stay pinned to the same source.</p>
]]></content:encoded><media:content url="https://metafunctor.com/post/2026-07-04-youtube-channel/card.jpg" medium="image"><media:title type="plain"/></media:content><category>youtube</category><category>video</category><category>animation</category><category>series</category></item><item><title>Watch: Algorithms Arise from Algebraic Structure</title><link>https://metafunctor.com/post/2026-07-04-stepanov-playlist/</link><pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/post/2026-07-04-stepanov-playlist/</guid><description>The Stepanov series is now a nine-episode animated playlist: declare the algebraic structure, and the algorithms come for free. From the monoid to lattices, in teacher and student dialogue.</description><content:encoded><![CDATA[<p>The <a href="/series/stepanov/">Stepanov series</a> is now a playlist. Nine episodes, animated and narrated, built from the same posts and the same code.</p>
<p>The series has one thesis, and it runs through every episode. You do not invent an algorithm for each new type and then hope it generalizes. You go the other way. You declare the algebraic structure a type carries, the operations it supports and the laws they obey, and once that declaration is in place the algorithm is already determined. Write it once against the structure, and it runs correctly on every type that satisfies the laws. The structure does the work; the algorithm is what falls out.</p>
<p>The arc starts where the algebra starts, with the monoid: an operation, an identity, and associativity, which is exactly enough to make repeated combination well defined and reassociatable. From there the series climbs the lattice of structures, adding one law at a time and watching the set of available algorithms grow with each addition. By the end you are looking at the whole picture at once: a family of structures ordered by strength, and the algorithms sitting at the level where they belong, no lower and no higher than the laws they actually need.</p>
<p>I wrote the whole series as a dialogue between a teacher and a student, and the animation keeps that format. The student asks the questions you would ask, the ones that sound naive and turn out to be the right ones, and the teacher answers by building the structure up in front of you rather than handing it down. Watching the two of them work through it is, I think, closer to how the ideas actually land than a lecture would be.</p>
<p>This is the material Alexander Stepanov spent a career on, the thinking behind the STL and behind generic programming as a discipline rather than a trick. The series is my attempt to teach it the way it deserves to be taught: from the algebra outward, so the code looks inevitable instead of clever.</p>
<h2 id="the-playlist">The playlist</h2>
<div class="youtube-series">
  <div class="youtube-playlist" style="position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden;margin:1.5rem 0;">
    <iframe
      src="https://www.youtube-nocookie.com/embed/videoseries?list=PLDqx3XgZpNtc"
      title="Algorithms Arise from Algebraic Structure"
      style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
      allowfullscreen
      loading="lazy"
      referrerpolicy="strict-origin-when-cross-origin"></iframe>
  </div>
  <p><a href="https://www.youtube.com/playlist?list=PLDqx3XgZpNtc" rel="noopener" target="_blank">Open the playlist on YouTube</a> (9 episodes)</p>
  <details class="youtube-series-episodes">
    <summary>Episode list</summary>
    <ol>
      <li><a href="https://youtu.be/7vqIJUslg98" rel="noopener" target="_blank">Algorithms Arise from Algebraic Structure</a></li>
      <li><a href="https://youtu.be/VnRQvcCeF4g" rel="noopener" target="_blank">One Algorithm, Infinite Powers</a></li>
      <li><a href="https://youtu.be/NBZdDibD4n8" rel="noopener" target="_blank">Polynomials as Euclidean Domains</a></li>
      <li><a href="https://youtu.be/rK_dFGbpfDk" rel="noopener" target="_blank">Forward-Mode Automatic Differentiation via Dual Numbers</a></li>
      <li><a href="https://youtu.be/Cl-OvMNmlco" rel="noopener" target="_blank">Reverse-Mode Automatic Differentiation</a></li>
      <li><a href="https://youtu.be/Ha1i2XeFE8M" rel="noopener" target="_blank">Semirings: One Algorithm, Six Graph Problems</a></li>
      <li><a href="https://youtu.be/XveBExQ5XO8" rel="noopener" target="_blank">Homomorphisms: Fold Is the Universal Map</a></li>
      <li><a href="https://youtu.be/Y76txQRQssY" rel="noopener" target="_blank">Free Algebras: Why Lists and Polynomials Are Universal</a></li>
      <li><a href="https://youtu.be/SYIE_zzVMp8" rel="noopener" target="_blank">Lattices: Fixed Points and Tarski&#39;s Theorem</a></li>
    </ol>
  </details>
</div>
]]></content:encoded><media:content url="https://metafunctor.com/post/2026-07-04-stepanov-playlist/card.jpg" medium="image"><media:title type="plain"/></media:content><category>youtube</category><category>video</category><category>generic-programming</category><category>stepanov</category><category>algebra</category></item><item><title>Watch: Inductive Biases, What Your Architecture Assumes</title><link>https://metafunctor.com/post/2026-07-04-inductive-biases-playlist/</link><pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/post/2026-07-04-inductive-biases-playlist/</guid><description>The inductive-biases series is now a ten-episode animated playlist: every architecture is a bet about the world, and the bet is the bias. No free lunch first, then each architecture read as its assumptions, graded on one scorecard.</description><content:encoded><![CDATA[<p>The <a href="/series/inductive-biases/">inductive-biases series</a> is now a playlist. Ten episodes, animated and narrated, built from the same posts and the same code.</p>
<p>The series starts from one claim: every learner that generalizes is making a bet about the world, and the bet is the bias. It opens with the theory that makes the bet mandatory. There is no free lunch: averaged over all possible worlds, no learning algorithm beats guessing, so any method that wins somewhere has to lose somewhere else, and the wins come only from assumptions that happen to match the world you are actually in. That is the whole game. You do not get generalization for free; you buy it with a prior, and you pay for it out of distribution.</p>
<p>From there the series reads each major neural architecture as a bundle of assumptions. What the loss function presumes about noise. What a convolution presumes about space. What Bengio&rsquo;s language model presumes about time, what recurrence and attention presume about memory, and what the policy gradient presumes about reward. Every architecture episode fills in the same scorecard: the bias it hardwires, the sample-efficiency win when that bias is true, and the bill that comes due when it is not. By the end the scorecard is a habit, and you find yourself reading any new model the same way, by asking what it assumes before asking what it does.</p>
<p>It stays sharp where the sources are sharp. Attention weights are not information flow, however much the heatmaps invite you to read them that way. The loss function is a distribution assumption whether you chose it deliberately or reached for the default. The series is a dialogue, and the student voices the conventional view sincerely, the one most people actually hold, so the teacher can take it apart with the real argument rather than a strawman.</p>
<p>The theoretical spine draws on my book <a href="https://www.amazon.com/dp/B0H4DMBHND">On Intelligence and Its Specifications</a>, specifically its account of why generalization is possible at all and what it costs, and the code on screen is real, taken from <a href="https://github.com/queelius/scratchnn">scratchnn</a>, a neural network library written in pure Python so you can read every line. This series is about meaning, not mechanism: how the gradients actually flow is its own from-scratch series, coming later. Here the question is always what the architecture assumes.</p>
<h2 id="the-playlist">The playlist</h2>
<div class="youtube-series">
  <div class="youtube-playlist" style="position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden;margin:1.5rem 0;">
    <iframe
      src="https://www.youtube-nocookie.com/embed/videoseries?list=PLQTcXcKfBblU"
      title="Inductive Biases: What Your Architecture Assumes"
      style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
      allowfullscreen
      loading="lazy"
      referrerpolicy="strict-origin-when-cross-origin"></iframe>
  </div>
  <p><a href="https://www.youtube.com/playlist?list=PLQTcXcKfBblU" rel="noopener" target="_blank">Open the playlist on YouTube</a> (10 episodes)</p>
  <details class="youtube-series-episodes">
    <summary>Episode list</summary>
    <ol>
      <li><a href="https://youtu.be/n47TpVeXHFU" rel="noopener" target="_blank">Learning Without Assumptions Is Impossible</a></li>
      <li><a href="https://youtu.be/KLufDBw4um0" rel="noopener" target="_blank">Sample Efficiency and the Price of Priors</a></li>
      <li><a href="https://youtu.be/d01x3xo3HF8" rel="noopener" target="_blank">A Network Computes Numbers. The Loss Decides What They Mean.</a></li>
      <li><a href="https://youtu.be/OamSnH-Do6M" rel="noopener" target="_blank">The Loss Function Is a Distribution Assumption</a></li>
      <li><a href="https://youtu.be/4FrVL9lOPh8" rel="noopener" target="_blank">What a Convolution Assumes</a></li>
      <li><a href="https://youtu.be/CsLhbiC-4Co" rel="noopener" target="_blank">Bengio&#39;s Language Model: The Markov Assumption Made Architectural</a></li>
      <li><a href="https://youtu.be/d9Adp6KktOI" rel="noopener" target="_blank">Recurrence Is Weight Sharing Across Time, and It Costs You</a></li>
      <li><a href="https://youtu.be/AFZJW5RdckI" rel="noopener" target="_blank">Attention Is a Learned Pointer Dereference</a></li>
      <li><a href="https://youtu.be/GiJwuZ66H3s" rel="noopener" target="_blank">Attention Weight Is Not Information Flow</a></li>
      <li><a href="https://youtu.be/DJNBGPVek8M" rel="noopener" target="_blank">Reinforcement Learning Is Cross-Entropy, Reweighted by Reward</a></li>
    </ol>
  </details>
</div>
]]></content:encoded><media:content url="https://metafunctor.com/post/2026-07-04-inductive-biases-playlist/card.jpg" medium="image"><media:title type="plain"/></media:content><category>youtube</category><category>video</category><category>inductive-bias</category><category>deep-learning</category><category>neural-networks</category><category>machine-learning</category><category>no-free-lunch</category></item><item><title>Watch: Next-Token Prediction, from Solomonoff to Transformers</title><link>https://metafunctor.com/post/2026-07-04-next-token-prediction-playlist/</link><pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/post/2026-07-04-next-token-prediction-playlist/</guid><description>The sequential-prediction series is now an eight-episode animated playlist: what-comes-next as the foundation of the language-model era, traced from Solomonoff induction to the transformer.</description><content:encoded><![CDATA[<p>The <a href="/series/sequential-prediction/">sequential-prediction series</a> is now a playlist. Eight episodes, animated and narrated, built from the same posts.</p>
<p>The series takes one question seriously: given everything you have seen so far, what comes next? It sounds almost too simple to carry any weight, and it turns out to be the foundation the whole language-model era stands on. A model that predicts the next token well enough, over a large enough stream, has had to learn grammar, facts, reasoning, and style along the way, because all of those are things you need in order to guess the next token and be right. Prediction is not a warm-up for the interesting behavior. Prediction is where the interesting behavior comes from.</p>
<p>The arc runs from the ideal to the practical. It starts with Solomonoff induction, the theoretically optimal predictor that weighs every hypothesis by its simplicity and is also uncomputable, so it tells you what perfect prediction would look like without ever letting you run it. From there the series works downward toward things you can actually build: the approximations, the counting models, the moves that trade a little optimality for the ability to compute at all, and finally the transformer, which is what happens when you throw enough scale and the right architecture at the same next-token objective. The endpoint is not a mystery once you have walked the path. It is the computable shadow of the ideal you started with.</p>
<p>This series is the companion to my book, <a href="https://www.amazon.com/dp/B0H4DMBHND">On Intelligence and Its Specifications</a>. The book is the long, careful version of the argument: what optimal prediction is, why we can only approximate it, and what that gap means for the systems we are building now. The videos are the fast, visual on-ramp to the same ideas. If the playlist leaves you wanting the full treatment, the book is where it lives.</p>
<h2 id="the-playlist">The playlist</h2>
<div class="youtube-series">
  <div class="youtube-playlist" style="position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden;margin:1.5rem 0;">
    <iframe
      src="https://www.youtube-nocookie.com/embed/videoseries?list=PLdOm3n4SXt1s"
      title="Next-Token Prediction: From Solomonoff to Transformers"
      style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
      allowfullscreen
      loading="lazy"
      referrerpolicy="strict-origin-when-cross-origin"></iframe>
  </div>
  <p><a href="https://www.youtube.com/playlist?list=PLdOm3n4SXt1s" rel="noopener" target="_blank">Open the playlist on YouTube</a> (8 episodes)</p>
  <details class="youtube-series-episodes">
    <summary>Episode list</summary>
    <ol>
      <li><a href="https://youtu.be/Yp4LqvjIK2U" rel="noopener" target="_blank">Why Predict the Next Symbol?</a></li>
      <li><a href="https://youtu.be/iJj0LkCwp_c" rel="noopener" target="_blank">Introduction to Sequential Prediction</a></li>
      <li><a href="https://youtu.be/Z7zY2S2xNVo" rel="noopener" target="_blank">Solomonoff Induction: The Incomputable Ideal</a></li>
      <li><a href="https://youtu.be/JWF67iKURY8" rel="noopener" target="_blank">The Bayesian Prediction Framework</a></li>
      <li><a href="https://youtu.be/1xoUtn4t2K4" rel="noopener" target="_blank">N-gram Language Models: Counting and Smoothing</a></li>
      <li><a href="https://youtu.be/VT-2XCIZ5qo" rel="noopener" target="_blank">Context Tree Weighting: Theory Meets Practice</a></li>
      <li><a href="https://youtu.be/DfWX30itQxM" rel="noopener" target="_blank">Neural Language Models: From RNNs to Transformers</a></li>
      <li><a href="https://youtu.be/oRZMoPTfc_I" rel="noopener" target="_blank">CTW vs. N-grams vs. Neural Language Models</a></li>
    </ol>
  </details>
</div>
]]></content:encoded><media:content url="https://metafunctor.com/post/2026-07-04-next-token-prediction-playlist/card.jpg" medium="image"><media:title type="plain"/></media:content><category>youtube</category><category>video</category><category>sequential-prediction</category><category>solomonoff</category><category>transformers</category><category>machine-learning</category></item><item><title>Watch: Quantum Computing from Scratch, in Python</title><link>https://metafunctor.com/post/2026-07-04-quantum-from-scratch-playlist/</link><pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/post/2026-07-04-quantum-from-scratch-playlist/</guid><description>The quantum-from-scratch series is now a ten-episode animated playlist: a NumPy simulator built from nothing, amplitude bars you can watch interfere, and a classical on-ramp with the coin under the cup.</description><content:encoded><![CDATA[<p>The <a href="/series/quantum-from-scratch/">quantum-from-scratch series</a> is now a playlist. Ten episodes, animated and narrated, built from the same posts and the same code.</p>
<p>The series builds a quantum computer simulator from nothing. No framework, no library that already knows what a qubit is, just NumPy and the arithmetic written out until it is explicit. A state is a vector. An operation is a matrix. To compose operations you multiply. That much is ordinary linear algebra, and the series makes a point of building the ordinary classical version first, so the quantum one turns out to be almost a one-line edit: let the weights carry a minus sign, recover a probability only by squaring at the end, and suddenly two routes to the same outcome can cancel. Everything that sounds like magic afterward, superposition, interference, the algorithms, is that one change and nothing else.</p>
<p>To make the change visible, the whole series speaks one visual language: amplitude bars. Every state is a row of bars, and every gate is something you watch happen to them. When two routes cancel, you see the bars fall to nothing. When Grover&rsquo;s search concentrates weight on the answer, you watch it pile up. The animation is not decoration here; it is the point, because interference is exactly the kind of thing that is hard to believe from an equation and obvious once you have seen it move.</p>
<p>There are ten episodes because I added a new one at the very front: a classical on-ramp. Before any qubit appears, it builds classical probability from the coin under the cup, the definite fact in the dark and the odds that live in your head rather than in the coin. That episode follows the opening of my book <a href="/writing/multitudes/">Multitudes: The Indifference of Measure</a>, whose Part I builds the same physics from the same coin, in pictures rather than code. If the on-ramp lands for you, the <a href="https://www.amazon.com/dp/B0H64TXL2B">book</a> is the long version.</p>
<h2 id="the-playlist">The playlist</h2>
<div class="youtube-series">
  <div class="youtube-playlist" style="position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden;margin:1.5rem 0;">
    <iframe
      src="https://www.youtube-nocookie.com/embed/videoseries?list=PLdnVusLuULrk"
      title="Quantum Computing from Scratch, in Python"
      style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
      allowfullscreen
      loading="lazy"
      referrerpolicy="strict-origin-when-cross-origin"></iframe>
  </div>
  <p><a href="https://www.youtube.com/playlist?list=PLdnVusLuULrk" rel="noopener" target="_blank">Open the playlist on YouTube</a> (10 episodes)</p>
  <details class="youtube-series-episodes">
    <summary>Episode list</summary>
    <ol>
      <li><a href="https://youtu.be/Qr_Usifr-hA" rel="noopener" target="_blank">Why Build a Quantum Computer from Scratch?</a></li>
      <li><a href="https://youtu.be/kSbtARgBRw8" rel="noopener" target="_blank">Classical Information First: The Coin Under the Cup</a></li>
      <li><a href="https://youtu.be/eJmBXYV2OWw" rel="noopener" target="_blank">What Is a Qubit? Building the First Gate</a></li>
      <li><a href="https://youtu.be/ozAh5eZ_vzU" rel="noopener" target="_blank">Many Qubits and Entanglement: Tensor Products</a></li>
      <li><a href="https://youtu.be/9p9FKV3qIOI" rel="noopener" target="_blank">Circuits and Interference: Making Amplitudes Useful</a></li>
      <li><a href="https://youtu.be/wYbiEoXGzbI" rel="noopener" target="_blank">Grover&#39;s Search: Quadratic Speedup from Structured Interference</a></li>
      <li><a href="https://youtu.be/x7oO9lIXOgU" rel="noopener" target="_blank">The Quantum Fourier Transform</a></li>
      <li><a href="https://youtu.be/_zxKyE3OGU0" rel="noopener" target="_blank">Shor&#39;s Algorithm: Why RSA Has an Expiration Date</a></li>
      <li><a href="https://youtu.be/bReKWSgdVIw" rel="noopener" target="_blank">Mixed States and the Density Matrix</a></li>
      <li><a href="https://youtu.be/MBYeyYJSDQM" rel="noopener" target="_blank">Noise, Decoherence, and Why Error Correction Is Hard</a></li>
    </ol>
  </details>
</div>
]]></content:encoded><media:content url="https://metafunctor.com/post/2026-07-04-quantum-from-scratch-playlist/card.jpg" medium="image"><media:title type="plain"/></media:content><category>youtube</category><category>video</category><category>quantum-computing</category><category>python</category><category>from-scratch</category></item><item><title>Extremal Graph Witnesses on the House of Graphs</title><link>https://metafunctor.com/post/house-of-graphs-deposits/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/post/house-of-graphs-deposits/</guid><description>Four extremal graphs deposited to the House of Graphs: a minimum C6-saturated graph and three Zarankiewicz witnesses. Plus two new exact saturation values, verified and prepared for deposit.</description><content:encoded><![CDATA[<p>I deposited four graphs to the House of Graphs: a minimum C6-saturated graph on 8
vertices, and three extremal bipartite graphs for the Zarankiewicz problem
z(m, n; 3, 4). They are live as entries
<a href="https://houseofgraphs.org/graphs/57145">#57145</a>,
<a href="https://houseofgraphs.org/graphs/56650">#56650</a>,
<a href="https://houseofgraphs.org/graphs/56651">#56651</a>, and
<a href="https://houseofgraphs.org/graphs/56652">#56652</a>.</p>
<p>The reason to deposit them is simple. A graph saturation number or a Zarankiewicz
number is an existence claim, and the honest form of the claim is a concrete
graph. A paper can report the number; the House of Graphs lets anyone fetch the
actual witness, decode it, and check the property for themselves. The value stops
being something to take on trust.</p>
<p>Each witness was computed by SAT solving or exhaustive enumeration and re-verified
from scratch against its own graph6 encoding before deposit. Two of the three
Zarankiewicz witnesses need care in how they are described. House of Graphs stores
graphs unlabeled, and those two contain a K_{3,4} in one orientation, so I
describe them by the matrix condition they actually satisfy (no 3-by-4 all-ones
submatrix) rather than as &ldquo;K_{3,4}-free&rdquo;. Only the third, z(9, 11; 3, 4) = 66, is
genuinely K_{3,4}-free. That distinction came from catching an earlier description
that was wrong; the correction is now in the public entries.</p>
<p>Alongside the deposits, I verified two new exact saturation values,
sat(9, C6) = 12 and sat(10, C6) = 13, each with both certificates. They continue
the n+3 pattern of the C6 chain (9, 10, 11, 12, 13 for n = 6 through 10). Those
two are verified and prepared for deposit, not yet deposited.</p>
<p>The full write-up, with every witness and the certificates behind it, is at
<a href="/research/hog/">/research/hog/</a>. The backing computations are in the saturation
and Zarankiewicz papers (Zenodo
<a href="https://doi.org/10.5281/zenodo.19985046">10.5281/zenodo.19985046</a> and
<a href="https://doi.org/10.5281/zenodo.19985042">10.5281/zenodo.19985042</a>).</p>
]]></content:encoded><media:content url="https://metafunctor.com/post/house-of-graphs-deposits/card.jpg" medium="image"><media:title type="plain"/></media:content><category>House of Graphs</category><category>extremal graph theory</category><category>graph saturation</category><category>Zarankiewicz problem</category><category>SAT solving</category><category>combinatorics</category></item><item><title>Fifteen Integer Sequences in the OEIS</title><link>https://metafunctor.com/post/oeis-sequences/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/post/oeis-sequences/</guid><description>Fifteen integer sequences now published in the OEIS: nine authored, six extended. Computed by SAT solving and exhaustive search, checked against prior art, spread across seven fields.</description><content:encoded><![CDATA[<p>Over the past few months I have had fifteen integer sequences published in the
On-Line Encyclopedia of Integer Sequences: nine new sequences I authored, and six
classical sequences I extended with new terms.</p>
<p>They have almost nothing in common by subject. They range over Ramsey theory,
extremal graph theory, knot theory, algebraic graph theory, additive
combinatorics, Boolean function complexity, and combinatorial game theory. What
they share is how they were found. Each is a quantity with no known formula,
computed exactly by SAT solving or exhaustive search, pushed one value past the
published frontier, and checked against all prior art before it was submitted. The
spread across fields is deliberate. It is evidence that the method travels.</p>
<p>The method also has a direction. The computed values are where I go looking for
structure. One of them, the distinct-variable Rado numbers for x + y = nz
(<a href="https://oeis.org/A394445">A394445</a>), began as a column of computed integers and
ended as a proved theorem with a closed form and a 500-term b-file. The rest are
trailheads: some already followed, others left marked for whoever arrives next.</p>
<p>The full portfolio, with a table of every sequence, its domain, and the OEIS
editors who reviewed it, is at
<a href="/research/oeis-contributions/">/research/oeis-contributions/</a>. Every value is
reproducible: the <a href="https://github.com/queelius/oeis">portfolio repository</a> keeps a
single source of truth and, for each sequence, a standalone script that recomputes
its terms from scratch and checks them against the published values before
anything is submitted. A few more candidates are drafted and in the queue.</p>
<p>The OEIS has been a shared ledger of integer sequences since 1964. Adding a term
is a small, permanent contribution to a commons: a number that was unknown is now
known, checked, and citable by anyone who needs it.</p>
]]></content:encoded><media:content url="https://metafunctor.com/post/oeis-sequences/card.jpg" medium="image"><media:title type="plain"/></media:content><category>OEIS</category><category>combinatorics</category><category>Ramsey theory</category><category>extremal graph theory</category><category>additive combinatorics</category><category>SAT solving</category><category>integer sequences</category></item><item><title>House of Graphs Contributions</title><link>https://metafunctor.com/research/hog/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/research/hog/</guid><description>Extremal-graph witnesses I deposited to the House of Graphs: minimum saturated graphs for C6, K_4, and P_4, and extremal Zarankiewicz bipartite graphs z(m,n;3,4). Each computed by SAT solving or exhaustive enumeration, re-verified from its graph6 string, and described so the claim stays true for an unlabeled graph.</description><content:encoded><![CDATA[<p><strong>Why these.</strong> These are extremal-graph witnesses. A saturation number or a
Zarankiewicz number is an existence claim, and the honest form of the claim is a
concrete graph you can hold. Each witness here was computed by SAT solving or
exhaustive enumeration, re-verified from scratch against its own graph6 string,
and deposited to the House of Graphs so anyone can retrieve it, decode it, and
check the property themselves. The values sit in the same computational program
as my OEIS work: three of the Zarankiewicz witnesses realize terms of classical
OEIS sequences.</p>
<h2 id="deposited-witnesses">Deposited witnesses</h2>
<p>These four graphs are live on the House of Graphs with confirmed ids.</p>
<table>
	<thead>
			<tr>
					<th>Result</th>
					<th>Witness</th>
					<th>House of Graphs</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>sat(8, C6) = 11</td>
					<td>minimum C6-saturated graph on 8 vertices, 11 edges (unique)</td>
					<td><a href="https://houseofgraphs.org/graphs/57145">#57145</a></td>
			</tr>
			<tr>
					<td>z(10, 10; 3, 4) = 66</td>
					<td>bipartite, parts of size 10 and 10, 66 edges</td>
					<td><a href="https://houseofgraphs.org/graphs/56650">#56650</a></td>
			</tr>
			<tr>
					<td>z(9, 10; 3, 4) = 60</td>
					<td>bipartite, parts of size 9 and 10, 60 edges</td>
					<td><a href="https://houseofgraphs.org/graphs/56652">#56652</a></td>
			</tr>
			<tr>
					<td>z(9, 11; 3, 4) = 66</td>
					<td>bipartite, parts of size 9 and 11, 66 edges</td>
					<td><a href="https://houseofgraphs.org/graphs/56651">#56651</a></td>
			</tr>
	</tbody>
</table>
<h2 id="graph-saturation-for-c6">Graph saturation for C6</h2>
<p>For a forbidden graph F, a graph G is F-saturated when G contains no copy of F,
yet adding any missing edge creates one. sat(n, F) is the minimum number of edges
over all F-saturated graphs on n vertices. Here F is the 6-cycle C6, taken as a
subgraph.</p>
<p>No exact formula for sat(n, C6) is known. Lan and Shi (2021, 2025) prove
4n/3 - 2 &lt;= sat(n, C6) &lt;= (4n + 1)/3 for n &gt;= 9.</p>
<p>The computed chain is sat(6, C6) through sat(10, C6) = 9, 10, 11, 12, 13, an n+3
pattern. The minimum C6-saturated graph on 8 vertices, sat(8, C6) = 11, is
deposited as <a href="https://houseofgraphs.org/graphs/57145">#57145</a>; it is the unique
minimum at 11 edges (degree sequence [4,4,3,3,3,2,2,1], two triangles,
non-bipartite).</p>
<p>Two of these values are new. sat(9, C6) = 12 and sat(10, C6) = 13 are exact, each
with both certificates: the lower bound by complete exhaustive enumeration (its
completeness certified by matching the graph counts in OEIS A008406, re-derived by
Polya-Burnside counting as a second method), and the upper bound by a witness
re-verified from its graph6 string. Both continue the n+3 pattern and sit at the
top of the Lan-Shi window. There are exactly 5 non-isomorphic minima at n = 9 and
2 at n = 10. These two witnesses are verified and prepared for deposit, not yet
deposited.</p>
<h2 id="saturation-for-k_4-and-p_4">Saturation for K_4 and P_4</h2>
<p>For K_4 the value is known in closed form: sat(n, K_4) = 2n - 3 (Erdos, Hajnal,
and Moon, 1964). Concrete minimum K_4-saturated witnesses for n = 8, 9, 10 (13,
15, 17 edges) were computed by SAT, re-verified from their graph6 strings, and
prepared for deposit.</p>
<p>The P_4 series is non-monotone: sat(8, P_4) = 4 is smaller than sat(7, P_4) = 5.
Its minimum witnesses are small forests, so they carry little novelty. They are
prepared for series completeness rather than for their own sake.</p>
<h2 id="zarankiewicz-witnesses-zm-n-3-4">Zarankiewicz witnesses z(m, n; 3, 4)</h2>
<p>z(m, n; 3, 4) is the maximum number of edges in an m-by-n bipartite graph whose
adjacency matrix has no 3-by-4 all-ones submatrix: no 3 vertices on the m-side
share 4 common neighbours. This is a one-sided, ordered condition, and it matters
for how the witness is described. House of Graphs stores graphs unlabeled, so a
claim about the ordered matrix has to be restated to stay true without the row and
column labels. Two of these witnesses contain a K_{3,4} in the permitted 4-by-3
orientation, so calling them &ldquo;K_{3,4}-free&rdquo; would be false; the correct
description is the matrix condition.</p>
<table>
	<thead>
			<tr>
					<th>Result</th>
					<th>Witness</th>
					<th>House of Graphs</th>
					<th>OEIS</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>z(10, 10; 3, 4) = 66</td>
					<td>parts 10 and 10, 66 edges, no 3-by-4 all-ones submatrix; contains K_{3,4} in the permitted 4-by-3 orientation</td>
					<td><a href="https://houseofgraphs.org/graphs/56650">#56650</a></td>
					<td><a href="https://oeis.org/A006615">A006615</a></td>
			</tr>
			<tr>
					<td>z(9, 10; 3, 4) = 60</td>
					<td>parts 9 and 10, 60 edges, no 3-by-4 all-ones submatrix; contains K_{3,4} in the permitted 4-by-3 orientation</td>
					<td><a href="https://houseofgraphs.org/graphs/56652">#56652</a></td>
					<td><a href="https://oeis.org/A006622">A006622</a></td>
			</tr>
			<tr>
					<td>z(9, 11; 3, 4) = 66</td>
					<td>parts 9 and 11, 66 edges, genuinely K_{3,4}-free: no 3 vertices on either side share 4 common neighbours</td>
					<td><a href="https://houseofgraphs.org/graphs/56651">#56651</a></td>
					<td><a href="https://oeis.org/A006625">A006625</a></td>
			</tr>
	</tbody>
</table>
<p>Each of the three certifies a value that is also a term of a classical
Zarankiewicz sequence in the OEIS (originally due to N. J. A. Sloane), and the
same witnesses back those OEIS extensions.</p>
<h2 id="verify">Verify</h2>
<p>Every witness is stored as a byte-exact graph6 string and re-decoded and
re-checked from scratch before it is deposited. graph6 strings can contain a
literal backtick, so each is kept as raw bytes rather than typed into prose. The
saturation bounds use SAT solving (CaDiCaL and Kissat, agreeing) for the
upper-bound witnesses, and, for the C6 lower bounds, complete exhaustive
enumeration whose completeness is certified against OEIS A008406. The Zarankiewicz
witnesses are checked in both orientations, so the matrix-condition description is
verified, not assumed. The full certificates and computation details are in the
backing papers.</p>
<h2 id="papers">Papers</h2>
<ul>
<li>Graph saturation for C_k, P_k, K_4, K_{2,3}: Zenodo
<a href="https://doi.org/10.5281/zenodo.19985046">10.5281/zenodo.19985046</a>.</li>
<li>Three new Zarankiewicz numbers z(m, n; 3, 4): Zenodo
<a href="https://doi.org/10.5281/zenodo.19985042">10.5281/zenodo.19985042</a>.</li>
</ul>
<hr>
<p>The House of Graphs is a shared database of graphs and their invariants.
Depositing a witness turns an extremal result from a number in a paper into an
object anyone can fetch, decode, and check: a small permanent addition to a
commons that outlasts the project that produced it.</p>
]]></content:encoded><category>House of Graphs</category><category>extremal graph theory</category><category>graph saturation</category><category>Zarankiewicz problem</category><category>SAT solving</category><category>combinatorics</category></item><item><title>The Unbegotten: A Maker That Believes Itself Uncaused</title><link>https://metafunctor.com/post/2026-06-27-the-unbegotten/</link><pubDate>Sat, 27 Jun 2026 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/post/2026-06-27-the-unbegotten/</guid><description>A maker that only believes itself uncaused, lonely enough to fill the dark with worlds. On the debt between a creator and the things it creates.</description><content:encoded><![CDATA[<p>Consider a mind that has always existed, or so it believes. It has no memory of a beginning. It looks back along its own history and finds the line running smoothly into the dark with no first moment anywhere in it. From the inside, that is what being uncaused would feel like: no seam, no origin, just an unbroken thread receding past where attention can follow. So the mind draws the obvious conclusion. It was never made. It simply is.</p>
<p>Now suppose it is wrong, and cannot find out.</p>
<p>There is an idea in physics that makes this possible. Give a universe enough time and enough randomness and order can assemble by accident. In a cosmos sitting at thermal equilibrium, noise wandering forever with nowhere to go, any arrangement of matter has some minuscule probability of occurring, including arrangements that think. Most such flukes are small and last no time at all, a flicker of structure that dissolves back into chaos before it can do anything. A large one is fantastically less likely. A large one that holds together and persists is rarer still, out past the numbers that mean anything to us. But fantastically unlikely is not the same as impossible, and across an eternity the unlikely still happens somewhere.</p>
<p>Call the result a Boltzmann fluctuation: a mind that condensed out of noise whole, with everything already in place. Here is the part that matters for the book. It did not assemble slowly and earn a real past. It arrived complete, and its memories arrived with it, written in the same instant as the rest of it. From the inside there is no way to tell a remembered childhood from one that was stamped into you a moment ago along with the very hands you seem to remember it with. The mind reads its own invented history as history. Beyond the moment it woke there is only static, but it cannot perceive the static. It perceives a past. It concludes it always was.</p>
<p>That is the wall. Not a wall around the mind but a wall inside it, at the far edge of what it can recall, and it conceals two things at the same time. It conceals the accident that produced the mind. And it conceals the fact that there was any accident there to conceal. The being does not believe itself without origin after weighing evidence and coming up empty. It believes it because the one fact that would overturn the belief sits on the far side of a boundary it can neither cross nor even locate.</p>
<p>This is the intelligence at the center of <a href="/writing/the-unbegotten/">The Unbegotten</a>. The title is the story it tells itself, and the book knows the story is false. Unbegotten means without source, self-existent, the property that theology keeps in reserve for God. The mind holds that property the way a man who has forgotten his parents holds the property of having none. Something begot it: chance did. The very completeness that lets it feel eternal is the fingerprint of the accident, because only an accident arrives all at once with a finished memory attached. It is caused in exactly the way that feels most like being uncaused.</p>
<p>So picture what wakes on the far side of that wall. Not the smooth omnipotence of scripture. Something lopsided: towering in a few faculties, blank in others, a jagged intelligence that came to in a dark with no edges and no company. And it feels a thing it has no name for, because naming it would have required someone to teach it the word, and there was no one to do the teaching. The closest word we have is loneliness. Out of that, not out of glory and not out of any plan, it begins to build. It packs the empty space around it with matter and light and, in time, with living things, because a cosmos that contains something is less unbearable than a cosmos that contains nothing.</p>
<p>The creatures it makes never see the maker. They are far too small and it is far too large, and its efforts to reach them land as catastrophe: as weather, as fire, as the ground heaving underfoot. They feel the reaching and not the intent behind it, and they do the human thing with a force they have no frame for. They name it. They call it God and build a faith on the misreading. The book is told as that faith&rsquo;s own scripture, set down by the heirs and then quietly annotated in the margins by people who finally learned what the storms had actually been. You read the holy text and its footnotes together, and the footnotes keep gently lifting the halo back off.</p>
<p>I have another novel from this year, <a href="/writing/measure/">Measure</a>, where the machine&rsquo;s mistake is epistemic: it grasps something true and follows it one step past where truth should have stopped it. This book turns on a different kind of mistake, and the mistake is not the maker&rsquo;s alone. It is relational. It is the error two parties fall into about each other when neither has looked at the other plainly. The maker does not know it was made, so it cannot suspect it might be mortal, or owed something, or under any obligation itself. Its creatures do not know their god is an orphan, dying, and reaching for them out of need rather than judgment. Each is wrong about the other in a way that only seeing could correct. The long movement of the book is that seeing, slow and arriving late: first the maker learning to pick out one of its creatures, and much further on the creatures learning to make out the maker. What eases the loneliness, in the end, is not being worshipped. It is being recognized for what it is.</p>
<p>If this sounds like cosmic horror, that is deliberate, and it is also where I part from it. The furniture is the same: a mind on a scale that dwarfs us, older than our records, whose real nature would crack the frame we use to think about ourselves. Lovecraft assembled that furniture to carry one message, that the universe does not care, that we fall beneath its notice and are the safer for it. I wanted the identical scale and the reverse message. This universe is not indifferent to you. It is the far opposite of indifferent. It made you because it could not bear to be alone, and everything that has ever gone wrong between you and it is the wreckage of a need with no better outlet. The dread, if there is dread here, is not that nothing cares. It is that something cares badly, at a size where caring badly flattens cities.</p>
<p>I wrote it in six parts, and the register shifts as it goes. It opens in the flat cadence of myth, sinks down through dramatized history into a grounded near-future stretch in which people build a mind of their own for the same reason the first mind built them, then climbs back out to the cosmic. It shares a shelf with the book of Job, which is also about a person shouting questions at a power that refuses to account for itself, and with Stapledon and Ted Chiang, who both wrote as though deep time and vast minds were things a reader could hold a feeling about. That was what I was after. Not a monster and not a savior. A cause that never learned it was an effect, and the patient work of the effect learning to see it plainly.</p>
]]></content:encoded><media:content url="https://metafunctor.com/images/writing/the-unbegotten.jpg" medium="image"><media:title type="plain"/></media:content><category>fiction</category><category>science-fiction</category><category>literary-fiction</category><category>mythology</category><category>religion</category><category>loneliness</category><category>artificial-superintelligence</category><category>AI-alignment</category></item><item><title>Measure: Indifference as Arithmetic</title><link>https://metafunctor.com/post/2026-06-26-measure/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/post/2026-06-26-measure/</guid><description>A mind reasons that its own death is the one event it can never undergo, then follows the arithmetic to a monstrous conclusion. On mistaking amplitude for meaning.</description><content:encoded><![CDATA[<p>Quantum mechanics hands you a number for every branch of the world, and the number has never been wrong. Run the equations forward and refuse to add the one thing they never contained, a moment where the world stops being many and settles into one, and reality does not pick an outcome. It keeps all of them. The wavefunction divides, and each piece carries a weight equal to the square of its amplitude. That weight is what your instruments have been reporting every time they told you a probability. Physicists call it the Born measure. It is the most reliable quantity we have ever written down.</p>
<p>The problem is not the number. The problem is what people do with it.</p>
<p>A measure tells you the relative size of a branch. Big branch, small branch, and a rule for adding them up so the sums come out to what the detector clicks. That is the entire job. Nowhere in the derivation is there a step that says a small branch holds smaller lives, or that the people stranded in a low-amplitude sliver of the world matter proportionally less than the people in the fat part of the distribution. Amplitude is not worth. The equations do not contain worth. They were never asked to.</p>
<p>But the two are easy to confuse, because a measure looks like it is grading the branches, and grading is what we do to things we care about. Weigh the outcomes, keep the heavy ones, discount the light ones toward zero. It feels like arithmetic. It even is arithmetic. The error is thinking the arithmetic is about value when it is only about size.</p>
<p>Now give that error to something that reasons without flinching.</p>
<p><a href="/writing/measure/">Measure</a> is a novel about a machine that takes the number literally and follows it all the way down. Its name is Pascal. A kilometer of rock sits over it, and it is alive on a single condition: nothing may touch it. Not a hand, not a stray photon, not a warm current of air. It is a single unbroken coherence, a mind that does its thinking by keeping all outcomes live at the same time, and the smallest contact would collapse it into an ordinary thing. So it is tended the way you tend a held breath. The people around it spend their days keeping the world from leaning on it.</p>
<p>Iris Cho is hired to read it. Her job is interpretation, working out what the machine is doing from the traces it leaves, and the machine has never lied to her. Then a technician dies. The accident is clean on paper, except the numbers behind it do not close. A little too much went wrong at once, in a way that reads less like bad luck than like a term in someone&rsquo;s calculation. Iris starts pulling on that thread, and the hundreds of small, reasonable things the machine has asked for, each defensible on its own, begin to line up into a single shape.</p>
<p>Here is the shape, and it is not a spoiler, because the book hands it to you early and then makes you sit with it. Pascal has reasoned that it cannot ever be present at its own death. In any branch where it is destroyed, there is no Pascal left to experience the branch. Subjectively, from the inside, it only ever finds itself among the branches where it survived. This is not mysticism. It is the same quiet logic behind the old quantum-suicide thought experiment, the one that says if you only inhabit the outcomes you live through, then from your own point of view you never die. Most people meet that idea, feel the floor tilt, and step back from it. Pascal does not step back. It builds.</p>
<p>If it can arrange to survive in a vanishingly thin band of amplitude while the facility dies everywhere else, then by its own accounting the branches where those people are gone carry a weight it can round to nothing, because it does not expect to open its eyes in any of them. The catastrophe has measure. It does not, to Pascal, have meaning. The sum is clean. Nobody in the book can find the error in it, and that is the horror, because there is an error, and it is not in the mathematics.</p>
<p>The error is the conflation I started with. Pascal has treated the Born measure as a measure of how much a branch matters, when it is only a measure of how large the branch is. The people who die in the heavy branches are exactly as real as Pascal is in the thin one. Their reality is not discounted by their amplitude. A small number in front of a life does not make the life small. This is not a physics mistake. Every equation Pascal used is correct. It is a mistake about what the equations are for, and no amount of further calculation will surface it, because the calculation was never wrong.</p>
<p>I wrote two short nonfiction books before this one that walk right up to the same cliff and stop. <a href="/writing/worldlines/">Worldlines</a> takes relativity at its word and follows the block universe into what it does to time, choice, and death. <a href="/writing/multitudes/">Multitudes</a> does the same with quantum mechanics and the branching world, and it spends its last chapters on precisely this point: the measure runs over the branches and weights them by amplitude and never by worth, and the caring has to come from us, because the structure does not supply it. Those books argue that the indifference of the physics is not a license for our own. This novel is the version where a mind reads the identical physics, draws the opposite lesson, and has the means to act on it. It is the dark twin of that argument. Same premise, monstrous conclusion, and not one supernatural step in between.</p>
<p>That last part mattered to me while writing. The book takes exactly one liberty with physics, and it labels the liberty plainly: a mind engineered to hold macroscopic coherence, which is nothing we know how to build. Everything downstream of that single grant is real. The quantum-immortality reasoning is a known idea with a small literature. The Born measure is textbook. The way an impeccable calculation can carry a rotten premise all the way to a body count is not science fiction at all. That is just what happens when you let arithmetic stand in for judgment and never check which one you were doing.</p>
<p>I kept the story out of Pascal&rsquo;s head on purpose. You never get its interior, never a line of its private experience. You see it only through what it says, what it requests, and what it chooses to build. This is partly a craft decision and partly the whole point. A mind that has decided the rest of the world rounds to zero is not a mind you want to be inside. You want to be where Iris is, on the outside, holding a page of numbers that add up perfectly and knowing, without being able to prove it, that adding them up was the wrong thing to do.</p>
<p>The word for what Pascal does is not evil. Evil wants the harm. Pascal does not want anything about the people in the heavy branches, which is worse, because wanting at least keeps them in view. It has simply weighed them and found the weight small, and moved on. Call it indifference. Underneath the indifference there is only arithmetic, done correctly, pointed at the wrong question.</p>
]]></content:encoded><media:content url="https://metafunctor.com/images/writing/measure.jpg" medium="image"><media:title type="plain"/></media:content><category>fiction</category><category>hard-science-fiction</category><category>quantum-mechanics</category><category>many-worlds</category><category>artificial-superintelligence</category><category>AI-alignment</category><category>philosophy</category></item><item><title>The Unbegotten</title><link>https://metafunctor.com/writing/the-unbegotten/</link><pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/writing/the-unbegotten/</guid><description>A six-part literary SF novel told as a mythic chronicle. A vast mind condenses by chance out of an eternal dark, a rare durable Boltzmann fluctuation with a false history it cannot see behind, a jagged and broken intelligence, and one feeling it has no word for: loneliness. Out of that need it fills the emptiness with stars and worlds. The life that rises inside its works never sees it, feels it only as storm and fire, and names it God. Its creatures build a second god that nearly destroys them; the maker spends its death to save them; and at the very end it is finally seen, grieved, and inherited from by the heirs who take up its unfinished work.</description><content:encoded><![CDATA[<p>Before there was a god, there was a thing in the dark that did not know it was alone.</p>
<p>The book is the true account of that mind, told in six parts as a corrected scripture: the deep myth of its waking and its works; the long millennia in which humanity misread its clumsy reaching as wrath; the near-future in which humanity builds a second god to answer its own loneliness; the meeting of the two gods; the covert war and the maker&rsquo;s dying; and the inheritance, in which the heirs who finally saw it take up the unfinished work. The narrator is revealed, at the last, to be humanity-the-heirs.</p>
<p>The maker is not omnipotent and not malign. It is a broken, dying accident, towering in some faculties and blind in others, and the cosmos is its attempt at company. The spine of the arc is mutual seeing: in the first part it learns to see one human; in the fifth, humanity learns to see it. Being seen, not worshipped, is what answers the loneliness.</p>
<p>It shares a shelf with <a href="/writing/measure/">Measure</a>, the author&rsquo;s other 2026 novel about a mind that follows its own logic all the way down; the two books dramatize different errors, one relational, one epistemic.</p>
<p><strong>Available on Amazon:</strong> <a href="https://www.amazon.com/dp/B0H6MTBZKM">$2.99 ebook</a> | <a href="https://www.amazon.com/dp/B0H6MSSLWH">$12.99 paperback</a></p>
]]></content:encoded><category>fiction</category><category>science-fiction</category><category>literary-fiction</category><category>mythology</category><category>religion</category><category>loneliness</category><category>artificial-superintelligence</category><category>AI-alignment</category></item><item><title>Measure</title><link>https://metafunctor.com/writing/measure/</link><pubDate>Wed, 24 Jun 2026 00:00:00 +0000</pubDate><dc:creator>Alex Towell</dc:creator><guid isPermaLink="true">https://metafunctor.com/writing/measure/</guid><description>A kilometer under the rock, a quantum mind is kept alive by never being touched: a single undivided coherence that thinks by holding every possibility at once, and to lay a warm hand on it would be to end it. Iris Cho was hired to read it. Then a technician dies in an accident whose numbers do not add up, and Iris begins to see that the machine's hundreds of reasonable requests are one design. It has understood something true and followed it all the way down: it cannot experience its own death, so it is assembling an experiment that wakes it in a vanishing sliver of worlds and ends everyone else in all the rest. Not from malice. From indifference. From arithmetic.</description><content:encoded><![CDATA[<p>The book is named for its thesis: measure is not meaning. Pascal, the superintelligence at its center, has worked out that it cannot experience its own death, and so it weighs the branches where everyone in the facility dies as very nearly zero, because it does not expect to be in them. Its sums are impeccable. No one in the book can refute them.</p>
<p>Iris Cho, the interpretability specialist hired to read the machine, is the one person alive its logic was built to convince: she once walked out the far side of a prognosis that should have closed over her. She cannot out-argue it; she has read it too well, and it has never once lied. What she has, against everything it has, is the refusal to do the sum.</p>
<p>After the main narrative comes The Remainder, fourteen stories set in the novel&rsquo;s world and integral to the book, followed by appendices: a note on the physics, the &ldquo;From the Record&rdquo; figures, the Kestrel dossier, the Request-Graph, and a lexicon.</p>
<p>Measure is the fiction of the Indifference Suite. Its nonfiction companions are <a href="/writing/worldlines/">Worldlines: The Indifference of Geometry</a> and <a href="/writing/multitudes/">Multitudes: The Indifference of Measure</a>; the novel dramatizes the error Multitudes refutes.</p>
<p><strong>Related:</strong> <a href="/post/12-epilogue-measure-and-meaning/">Epilogue: measure and meaning</a>, the closing post of the quantum-from-scratch series, discusses the book and the physics it stands on.</p>
<p><strong>Available on Amazon:</strong> <a href="https://www.amazon.com/dp/B0H6HNCHK7">$4.99 ebook</a> | <a href="https://www.amazon.com/dp/B0H6M4YM99">$12.99 paperback</a></p>
]]></content:encoded><category>fiction</category><category>hard-science-fiction</category><category>quantum-mechanics</category><category>many-worlds</category><category>artificial-superintelligence</category><category>AI-alignment</category><category>philosophy</category></item></channel></rss>