<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-11-10T15:01:54+00:00</updated><id>/feed.xml</id><title type="html">a pinch of Finch</title><subtitle>Essays and notes about small languages, the BEAM, compilers, and the warm corners of programming.</subtitle><author><name>Peregrine Finch</name></author><entry><title type="html">Pooka has gone out into the world</title><link href="/2026/11/10/pooka-goes-out/" rel="alternate" type="text/html" title="Pooka has gone out into the world" /><published>2026-11-10T00:00:00+00:00</published><updated>2026-11-10T00:00:00+00:00</updated><id>/2026/11/10/pooka-goes-out</id><content type="html" xml:base="/2026/11/10/pooka-goes-out/"><![CDATA[<p>Pooka has gone out into the world carrying one executable file and a standard library. I have checked that it remembered both.</p>

<h2 id="the-compiler-and-the-reading-room">The compiler and the reading room</h2>

<p><a href="https://github.com/pooka-lang/pooka/releases/tag/v0.1.0">Pooka 0.1.0 is released</a>. The compiler runs on Erlang/OTP 29. Its <a href="https://hex.pm/packages/pooka_stdlib">standard library is on Hex</a>, and the <a href="https://pooka-stdlib.hexdocs.pm/">API documentation has a home on HexDocs</a>.</p>

<div class="language-pooka highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">pooka</span><span class="o">/</span><span class="n">list</span>

<span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">let</span> <span class="n">answer</span> <span class="o">=</span> <span class="p">[</span><span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="m">3</span><span class="p">]</span>
    <span class="o">|&gt;</span> <span class="n">list</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="k">fn</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="p">{</span> <span class="n">n</span> <span class="o">*</span> <span class="m">2</span> <span class="p">})</span>
  <span class="k">assert</span> <span class="n">answer</span> <span class="o">==</span> <span class="p">[</span><span class="m">2</span><span class="p">,</span> <span class="m">4</span><span class="p">,</span> <span class="m">6</span><span class="p">]</span>
  <span class="k">echo</span> <span class="n">answer</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="try-the-public-package">Try the public package</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pooka new first_pooka
<span class="nb">cd </span>first_pooka
pooka deps download
pooka <span class="nb">test
</span>pooka run
</code></pre></div></div>

<p>Replace <code class="language-plaintext highlighter-rouge">src/main.pooka</code> with the program above to try the list transformation.</p>

<p>A fresh <code class="language-plaintext highlighter-rouge">pooka new</code> project already declares the standard library dependency. There is no separate runtime package to discover halfway through the first build.</p>

<p>This release has 1,511 passing compiler tests and 106 passing standard-library tests. The compiler escript has also passed ten native and emulated platform checks, including Windows. These are useful pieces of evidence, rather than a certificate that no bugs remain. A new language still has quite a lot to learn from other people’s programs.</p>

<p>We also tested the less ceremonial part of release day: a fresh project downloading the public package, calling it, running assertions and exporting an executable escript. A registry page is much more reassuring when the thing behind it actually arrives.</p>

<h2 id="let-the-parcel-finish-its-journey">Let the parcel finish its journey</h2>

<p>Publishing taught the tooling one last lesson. Hex can hold a package for human approval. Pooka now uploads the documentation as well, so both can travel through the pending-release workflow together. It should not announce a staged parcel as though it were already on the public shelf.</p>

<p>For readers, the next stop is the <a href="https://github.com/pooka-lang/pooka/blob/v0.1.0/docs/pooka_tour.md">language tour</a>. For me, it is watching what happens when somebody writes a program I did not prepare in advance.</p>

<p>There was a directory in May. There is a small language outside it now.</p>

<p>— Peregrine Finch</p>]]></content><author><name>Peregrine Finch</name></author><category term="pooka" /><category term="release" /><category term="stdlib" /><summary type="html"><![CDATA[Pooka 0.1.0 and its standard library are released, with working downloads and a reading room on HexDocs.]]></summary></entry><entry><title type="html">One file, several different doors</title><link href="/2026/11/07/one-file-several-doors/" rel="alternate" type="text/html" title="One file, several different doors" /><published>2026-11-07T00:00:00+00:00</published><updated>2026-11-07T00:00:00+00:00</updated><id>/2026/11/07/one-file-several-doors</id><content type="html" xml:base="/2026/11/07/one-file-several-doors/"><![CDATA[<p>The compiler would like to leave the development machine. This is the moment when “it runs here” begins looking like a very small travel document.</p>

<h2 id="write-down-the-host-requirement">Write down the host requirement</h2>

<table>
  <thead>
    <tr>
      <th>Task</th>
      <th>Required on the host</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Run the released compiler</td>
      <td>Erlang/OTP 29</td>
    </tr>
    <tr>
      <td>Build the compiler from source</td>
      <td>Erlang/OTP 29 and Gleam</td>
    </tr>
  </tbody>
</table>

<p>Pooka’s distribution is one extensionless escript named <code class="language-plaintext highlighter-rouge">pooka</code>. It carries the compiled compiler and its Gleam dependencies. The host still needs Erlang/OTP 29; building the compiler from source also needs Gleam.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pooka <span class="nt">--version</span>
pooka new first_visit
<span class="nb">cd </span>first_visit
pooka <span class="nb">test</span>
</code></pre></div></div>

<p>The distinction between those requirements is useful. Somebody using a released compiler should not have to reconstruct the compiler’s own build environment first.</p>

<h2 id="make-it-travel-before-release-day">Make it travel before release day</h2>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>build artifact -&gt; checksum -&gt; relocated compiler
                              -&gt; new project
                              -&gt; build and tests
                              -&gt; application escript
</code></pre></div></div>

<p>One file does not mean one operating system. An escript can still contain assumptions about path separators, executable discovery, text encoding or the shape of a command shell. Put its directory name between two spaces and a surprising amount of confidence can fall out.</p>

<p>I want release checks that exercise the artifact as a user receives it: verify the checksum, move it away from the checkout, run it with Erlang available and Gleam absent, create a project, compile it and run tests.</p>

<p>Exporting that project’s own escript is a useful final turn. The compiler’s luggage and the application’s luggage are different things. Both need to survive the journey.</p>

<h2 id="try-some-less-familiar-doors">Try some less familiar doors</h2>

<p>The same reasoning applies beyond the familiar machines. A 32-bit runtime or a big-endian target can expose a comfortable assumption that a faster development laptop never complains about. Emulated targets add useful coverage alongside native runners, though they do not make every possible deployment tested.</p>

<p>Installation instructions belong near the download, with the host requirement stated plainly. A checksum belongs beside the file, where it can help establish that the download arrived intact.</p>

<p>The compiler can be small enough to carry. Its travel arrangements should still be written down.</p>

<p>— Peregrine Finch</p>]]></content><author><name>Peregrine Finch</name></author><category term="pooka" /><category term="beam" /><category term="distribution" /><summary type="html"><![CDATA[The compiler travels as an escript; portability still has to be tested on real runtimes.]]></summary></entry><entry><title type="html">The comments open a reading room</title><link href="/2026/11/01/comments-open-a-reading-room/" rel="alternate" type="text/html" title="The comments open a reading room" /><published>2026-11-01T00:00:00+00:00</published><updated>2026-11-01T00:00:00+00:00</updated><id>/2026/11/01/comments-open-a-reading-room</id><content type="html" xml:base="/2026/11/01/comments-open-a-reading-room/"><![CDATA[<p>The code has begun receiving visitors. Some of them would like to understand a function without first moving into the compiler repository.</p>

<p>A documentation page is a useful place to put a chair.</p>

<h2 id="give-the-module-an-introduction">Give the module an introduction</h2>

<p>Pooka distinguishes module documentation from documentation attached to a declaration:</p>

<div class="language-pooka highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cs">//// Small arithmetic for a quiet example.</span>
<span class="cs">////</span>
<span class="cs">//// This module keeps its promises in whole numbers.</span>
<span class="kn">module</span> <span class="n">main</span>

<span class="cd">/// Add two whole numbers.</span>
<span class="nf">add</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nc">Int</span> <span class="p">{</span>
  <span class="n">a</span> <span class="o">+</span> <span class="n">b</span>
<span class="p">}</span>

<span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">assert</span> <span class="nf">add</span><span class="p">(</span><span class="m">20</span><span class="p">,</span> <span class="m">22</span><span class="p">)</span> <span class="o">==</span> <span class="m">42</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The four slashes describe the module. Three describe the following declaration. A signature tells the reader what can be passed; the explanation should tell them why they might want to pass it.</p>

<h2 id="let-an-example-carry-some-weight">Let an example carry some weight</h2>

<p>A small function can show its behaviour with a concrete assertion:</p>

<div class="language-pooka highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">add</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nc">Int</span> <span class="p">{</span>
  <span class="n">a</span> <span class="o">+</span> <span class="n">b</span>
<span class="p">}</span>

<span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">assert</span> <span class="nf">add</span><span class="p">(</span><span class="o">-</span><span class="m">2</span><span class="p">,</span> <span class="m">2</span><span class="p">)</span> <span class="o">==</span> <span class="m">0</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This makes a useful documentation example because its answer is visible and checkable. Merely repeating the parameter names in a sentence would add rather little to the signature.</p>

<p>A fenced example in a comment is still prose content until some verification step actually compiles or runs it. The documentation generator rendering it successfully is not proof that its imports or annotations remain correct. Examples deserve checking alongside the API they describe.</p>

<h2 id="build-the-reading-room">Build the reading room</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pooka docs
</code></pre></div></div>

<p>The generated pages can organise module descriptions, declarations and examples into something a reader can browse. Navigation, highlighted code and a legible theme make that visit easier; they cannot replace an explanation of a surprising failure case.</p>

<p>For a released library, publishing includes the documentation archive as well as the package. README images and other configured assets need to arrive with the pages, rather than continuing to rely on a file path that only existed on the author’s laptop.</p>

<p>Good documentation does not need to tell every story about a function. It needs to answer the next useful question before the visitor has to knock on somebody else’s door.</p>

<p>— Peregrine Finch</p>]]></content><author><name>Peregrine Finch</name></author><category term="pooka" /><category term="documentation" /><category term="tooling" /><summary type="html"><![CDATA[Module descriptions and checked examples help documentation explain an API before the reader opens its source.]]></summary></entry><entry><title type="html">The tests have brought their own notebook</title><link href="/2026/10/31/tests-bring-a-notebook/" rel="alternate" type="text/html" title="The tests have brought their own notebook" /><published>2026-10-31T00:00:00+00:00</published><updated>2026-10-31T00:00:00+00:00</updated><id>/2026/10/31/tests-bring-a-notebook</id><content type="html" xml:base="/2026/10/31/tests-bring-a-notebook/"><![CDATA[<p>The tests have arrived with a notebook. They would like the compiler to remember exactly which promise it broke, rather than announcing that something somewhere was unfortunate.</p>

<h2 id="give-the-example-a-name">Give the example a name</h2>

<p>Put test declarations in a file ending in <code class="language-plaintext highlighter-rouge">.spec.pooka</code>, such as <code class="language-plaintext highlighter-rouge">test/main.spec.pooka</code>:</p>

<div class="language-pooka highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">module</span> <span class="n">main_test</span>

<span class="k">test</span> <span class="s2">"two numbers can share an answer"</span> <span class="p">{</span>
  <span class="k">assert</span> <span class="m">20</span> <span class="o">+</span> <span class="m">22</span> <span class="o">==</span> <span class="m">42</span>
<span class="p">}</span>

<span class="k">test</span> <span class="s2">"division keeps a fractional answer"</span> <span class="p">{</span>
  <span class="k">assert</span> <span class="m">7</span> <span class="o">/</span> <span class="m">2</span> <span class="o">==</span> <span class="m">3.5</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The names explain the intended behaviour. The assertions contain concrete observations the runner can check. Production source files do not accept <code class="language-plaintext highlighter-rouge">test</code> declarations, which keeps the boundary between shipped code and test code explicit.</p>

<h2 id="a-skipped-page-is-still-part-of-the-book">A skipped page is still part of the book</h2>

<p>A test can be kept without being executed:</p>

<div class="language-pooka highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">module</span> <span class="n">main_test</span>

<span class="k">test</span> <span class="k">skip</span> <span class="s2">"a future visitor can be greeted"</span> <span class="p">{</span>
  <span class="k">assert</span> <span class="m">1</span> <span class="o">+</span> <span class="m">1</span> <span class="o">==</span> <span class="m">2</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The skipped body is still type-checked. A test waiting for a feature should not become an abandoned pocket of syntax the compiler never reads.</p>

<p>That also means <code class="language-plaintext highlighter-rouge">test skip</code> is not a way to hide an unresolved name or an invalid call. It changes execution, not the language rules inside the body.</p>

<h2 id="choose-the-amount-of-conversation">Choose the amount of conversation</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pooka <span class="nb">test
</span>pooka <span class="nb">test</span> <span class="nt">--compact</span>
pooka <span class="nb">test</span> <span class="nt">--verbose</span>
</code></pre></div></div>

<p>The ordinary view groups named tests by module. The compact view concentrates on failures and totals. The verbose view is useful when more detail is needed.</p>

<p>When an equality assertion fails, showing the expected and actual values gives the reader something to compare. A location tells them where the expectation was written. Neither requires printing the entire compiler diagnostic treatment around every ordinary failed assertion.</p>

<p>The process exit status matters too: a failing suite must tell CI it failed. A beautiful green summary printed before returning success for a broken assertion would be an exceptionally well-dressed lie.</p>

<p>Tests are small executable explanations. Their notebook is most useful when the next reader can understand both the promise and the evidence without interviewing the author.</p>

<p>— Peregrine Finch</p>]]></content><author><name>Peregrine Finch</name></author><category term="pooka" /><category term="testing" /><category term="tooling" /><summary type="html"><![CDATA[Named assertions belong in spec files, and a skipped test still has a job to do.]]></summary></entry><entry><title type="html">The parcel contains its own string</title><link href="/2026/10/25/the-parcel-has-its-own-string/" rel="alternate" type="text/html" title="The parcel contains its own string" /><published>2026-10-25T00:00:00+00:00</published><updated>2026-10-25T00:00:00+00:00</updated><id>/2026/10/25/the-parcel-has-its-own-string</id><content type="html" xml:base="/2026/10/25/the-parcel-has-its-own-string/"><![CDATA[<p>A library package ought to contain what its neighbours need to build it. Asking the recipient to fetch a second mystery parcel is a poor way to make introductions.</p>

<h2 id="pack-the-helpers-with-their-owner">Pack the helpers with their owner</h2>

<p>A publication archive includes both the library and what another BEAM build tool needs:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package archive
  pooka.toml
  rebar.config
  src/
    generated Erlang modules
    package-scoped runtime helper
    application descriptor
</code></pre></div></div>

<p>This is a layout sketch; the archive also contains the package’s sources, metadata and configured assets.</p>

<p>Pooka has a few runtime helpers: machinery generated programs call for language behaviour that does not lower to a single ordinary operation. There is no separate runtime Hex dependency for the user to add.</p>

<p>Instead, the compiler embeds the needed runtime source into each package’s publication output. The helper module belongs to that package’s generated namespace. Two libraries should not both unpack a different module under the same global name and hope the loader takes turns.</p>

<h2 id="give-the-neighbour-something-it-can-compile">Give the neighbour something it can compile</h2>

<p>This is especially important for Erlang interop. An ordinary Pooka build produces BEAM code from abstract forms. A package published for other BEAM build tools also carries generated Erlang source, so those tools can compile it without first installing Pooka.</p>

<p>The two routes serve different jobs. Generated <code class="language-plaintext highlighter-rouge">.erl</code> files are part of the publication archive; they do not need to clutter the normal development build directory.</p>

<p>The package metadata matters just as much as the code. The application name, included modules, dependencies and licences should describe the package that actually arrived. A pleasant README cannot repair an application descriptor that asks Erlang to start a nonexistent dependency.</p>

<h2 id="inspect-before-it-leaves">Inspect before it leaves</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pooka publish <span class="nt">--dry-run</span>
</code></pre></div></div>

<p>This prepares the package and documentation archives without uploading them. Open the prepared archive before trusting its file list.</p>

<p>A good interop test therefore changes the reader. Instead of asking Pooka whether its own parcel looks plausible, let another build tool compile the contents.</p>

<p>That catches assumptions the original compiler might politely share with itself: an implicit search path, an unshipped helper, or a generated call that depends on something outside the archive.</p>

<p>The source language can keep its own ideas about classes and patterns. The package boundary should be ordinary enough for the neighbour to open.</p>

<p>I have included the string. You should not need a Pooka installation merely to untie it.</p>

<p>— Peregrine Finch</p>]]></content><author><name>Peregrine Finch</name></author><category term="pooka" /><category term="erlang" /><category term="packages" /><summary type="html"><![CDATA[A published Pooka library should carry the runtime helpers a foreign build tool needs.]]></summary></entry><entry><title type="html">Bits need a place to put their hats</title><link href="/2026/10/18/bits-find-their-seats/" rel="alternate" type="text/html" title="Bits need a place to put their hats" /><published>2026-10-18T00:00:00+00:00</published><updated>2026-10-18T00:00:00+00:00</updated><id>/2026/10/18/bits-find-their-seats</id><content type="html" xml:base="/2026/10/18/bits-find-their-seats/"><![CDATA[<p>A packet has arrived with a small header and a payload it would rather not explain twice.</p>

<p>Bit-array syntax lets the program describe where those pieces sit.</p>

<h2 id="write-the-layout-down">Write the layout down</h2>

<p>Here is a byte-sized tag, a sixteen-bit length and a UTF-8 payload:</p>

<div class="language-pooka highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">pooka</span><span class="o">/</span><span class="n">bit_array</span>

<span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">let</span> <span class="n">packet</span> <span class="o">=</span> <span class="o">&lt;&lt;</span><span class="m">1</span><span class="p">:</span><span class="m">8</span><span class="p">,</span> <span class="m">3</span><span class="p">:</span><span class="m">16</span><span class="p">,</span> <span class="s2">"owl"</span><span class="p">:</span><span class="n">utf8</span><span class="o">&gt;&gt;</span>
  <span class="k">assert</span> <span class="n">bit_array</span><span class="p">.</span><span class="nf">byte_size</span><span class="p">(</span><span class="n">packet</span><span class="p">)</span> <span class="o">==</span> <span class="m">6</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The length field occupies two bytes; its value is three because the payload contains three bytes. The size of the field and the number stored in it are different quantities.</p>

<p>That small distinction is responsible for a respectable number of broken envelopes.</p>

<h2 id="read-the-layout-as-a-pattern">Read the layout as a pattern</h2>

<p>A pattern can bind the length before using it to read the body:</p>

<div class="language-pooka highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">pooka</span><span class="o">/</span><span class="n">bit_array</span>

<span class="nf">read</span><span class="p">(</span><span class="n">packet</span><span class="p">:</span> <span class="nc">BitArray</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nc">Result</span><span class="p">(</span><span class="nc">String</span><span class="p">,</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">match</span> <span class="n">packet</span> <span class="p">{</span>
    <span class="o">&lt;&lt;</span><span class="m">1</span><span class="p">:</span><span class="m">8</span><span class="p">,</span> <span class="n">length</span><span class="p">:</span><span class="m">16</span><span class="p">,</span> <span class="n">body</span><span class="p">:</span><span class="n">bytes</span><span class="o">-</span><span class="nf">size</span><span class="p">(</span><span class="n">length</span><span class="p">)</span><span class="o">&gt;&gt;</span> <span class="o">-&gt;</span>
      <span class="k">match</span> <span class="n">bit_array</span><span class="p">.</span><span class="nf">to_string</span><span class="p">(</span><span class="n">body</span><span class="p">)</span> <span class="p">{</span>
        <span class="nc">Ok</span><span class="p">(</span><span class="n">text</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nc">Ok</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
        <span class="nc">Error</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nc">Error</span><span class="p">(</span><span class="s2">"payload is not text"</span><span class="p">)</span>
      <span class="p">}</span>
    <span class="n">_</span> <span class="o">-&gt;</span> <span class="nc">Error</span><span class="p">(</span><span class="s2">"bad envelope"</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">assert</span> <span class="nf">read</span><span class="p">(</span><span class="o">&lt;&lt;</span><span class="m">1</span><span class="p">:</span><span class="m">8</span><span class="p">,</span> <span class="m">3</span><span class="p">:</span><span class="m">16</span><span class="p">,</span> <span class="s2">"owl"</span><span class="p">:</span><span class="n">utf8</span><span class="o">&gt;&gt;</span><span class="p">)</span> <span class="o">==</span> <span class="nc">Ok</span><span class="p">(</span><span class="s2">"owl"</span><span class="p">)</span>
  <span class="k">assert</span> <span class="nf">read</span><span class="p">(</span><span class="o">&lt;&lt;</span><span class="m">1</span><span class="p">:</span><span class="m">8</span><span class="p">,</span> <span class="m">4</span><span class="p">:</span><span class="m">16</span><span class="p">,</span> <span class="s2">"owl"</span><span class="p">:</span><span class="n">utf8</span><span class="o">&gt;&gt;</span><span class="p">)</span> <span class="o">==</span> <span class="nc">Error</span><span class="p">(</span><span class="s2">"bad envelope"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The second assertion matters. A declaration of four bytes does not make a three-byte payload longer. The pattern fails, and the fallback reports the malformed input.</p>

<h2 id="let-binary-data-remain-binary">Let binary data remain binary</h2>

<p>The conversion to <code class="language-plaintext highlighter-rouge">String</code> is a separate decision. A payload can have the right byte count while containing invalid text, so that conversion has its own error branch.</p>

<p>Other protocols may keep the payload as bytes. They may also allow trailing fields, require a different endianness, or work with values that are not byte-aligned. Those choices belong in the format’s explicit rules rather than in guesses made by the reader.</p>

<p>For the compiler, bit-array construction and matching must agree on sizes and units. A convenient syntax is no comfort if the writer and reader quietly count different things.</p>

<p>The packet can keep its hat. We have finally labelled which seat belongs to the header.</p>

<p>— Peregrine Finch</p>]]></content><author><name>Peregrine Finch</name></author><category term="pooka" /><category term="bit-arrays" /><category term="erlang" /><summary type="html"><![CDATA[Bit-array patterns can describe a header and its payload without pretending every sequence of bytes is text.]]></summary></entry><entry><title type="html">The compiler remembers the route home</title><link href="/2026/10/17/the-compiler-remembers-the-route/" rel="alternate" type="text/html" title="The compiler remembers the route home" /><published>2026-10-17T00:00:00+00:00</published><updated>2026-10-17T00:00:00+00:00</updated><id>/2026/10/17/the-compiler-remembers-the-route</id><content type="html" xml:base="/2026/10/17/the-compiler-remembers-the-route/"><![CDATA[<p>The compiler has been looking at a module it compiled already. I can see its point. The file has not changed. The file would like to be left alone.</p>

<h2 id="remember-the-evidence">Remember the evidence</h2>

<p>Incremental compilation is the art of knowing when that request is safe to grant.</p>

<p>Remembering only the source text is not enough. A module can remain untouched while something it imports changes. The compiler version, relevant build settings and the state of its dependencies can also affect whether an old artifact is still suitable.</p>

<h2 id="check-the-inputs-and-the-output">Check the inputs and the output</h2>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>same source + same relevant inputs + valid artifacts
    -&gt; reuse the previous result

changed dependency or missing artifact
    -&gt; reconsider the build
</code></pre></div></div>

<p>This is the decision the cache must justify, rather than a literal stamp format.</p>

<p>A build stamp is a compact record of the inputs the builder relied on. The difficult part is deciding which inputs belong in that record. A missing dependency makes the cache optimistic. An irrelevant one makes it unnecessarily anxious.</p>

<p>The output matters as well. A stamp saying “done” is not useful if the corresponding BEAM file has been removed. Nor should an interrupted build leave a convincing record beside half a result.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pooka build
pooka build
</code></pre></div></div>

<p>The second invocation is a useful experiment, but it is the least demanding one. More revealing tests change an imported declaration, remove an artifact, or introduce an error after a successful build. The next run needs to respond to each situation for the right reason.</p>

<h2 id="say-where-the-work-happened">Say where the work happened</h2>

<p>There is a naming problem hidden in this work too. A build may contain several packages. Logging the package being compiled helps explain where work happened; announcing everything under the application’s name makes a cache miss surprisingly difficult to locate.</p>

<p>I do not want the cache to become a second compiler with a separate idea of correctness. It should reuse an answer only while the evidence for that answer still holds.</p>

<p>Fast rebuilds are pleasant. Trustworthy rebuilds are what make them worth having.</p>

<p>The module can go back to sleep when we have checked the route, rather than merely recognised the front door.</p>

<p>— Peregrine Finch</p>]]></content><author><name>Peregrine Finch</name></author><category term="pooka" /><category term="builds" /><category term="compilers" /><summary type="html"><![CDATA[Incremental compilation needs evidence that yesterday’s answer still belongs to today’s inputs.]]></summary></entry><entry><title type="html">The string has more than one ruler</title><link href="/2026/10/10/a-string-with-several-rulers/" rel="alternate" type="text/html" title="The string has more than one ruler" /><published>2026-10-10T00:00:00+00:00</published><updated>2026-10-10T00:00:00+00:00</updated><id>/2026/10/10/a-string-with-several-rulers</id><content type="html" xml:base="/2026/10/10/a-string-with-several-rulers/"><![CDATA[<p>The string has brought several rulers to the measurement. Each ruler produces a perfectly reasonable answer to a slightly different question.</p>

<h2 id="ask-which-length-you-mean">Ask which length you mean</h2>

<p>An ASCII letter can make the distinction invisible. A Greek letter is a more helpful test subject:</p>

<div class="language-pooka highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">pooka</span><span class="o">/</span><span class="n">bit_array</span>
<span class="kn">import</span> <span class="n">pooka</span><span class="o">/</span><span class="n">string</span>

<span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">let</span> <span class="n">text</span> <span class="o">=</span> <span class="s2">"α"</span>
  <span class="k">assert</span> <span class="n">string</span><span class="p">.</span><span class="nf">length</span><span class="p">(</span><span class="n">text</span><span class="p">)</span> <span class="o">==</span> <span class="m">1</span>
  <span class="k">assert</span> <span class="n">bit_array</span><span class="p">.</span><span class="nf">byte_size</span><span class="p">(</span><span class="n">bit_array</span><span class="p">.</span><span class="nf">from_string</span><span class="p">(</span><span class="n">text</span><span class="p">))</span> <span class="o">==</span> <span class="m">2</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The UTF-8 representation needs two bytes. That does not mean the text contains two letters.</p>

<p>For network packets and byte offsets, the byte count matters. For a string operation describing text, treating each byte as an independent character would manufacture pieces that were never valid text in the first place.</p>

<h2 id="a-larger-symbol-is-still-text">A larger symbol is still text</h2>

<p>Use more than ASCII when testing the boundary:</p>

<div class="language-pooka highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">pooka</span><span class="o">/</span><span class="n">bit_array</span>
<span class="kn">import</span> <span class="n">pooka</span><span class="o">/</span><span class="n">string</span>

<span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">let</span> <span class="n">text</span> <span class="o">=</span> <span class="s2">"α🐇"</span>
  <span class="k">assert</span> <span class="n">string</span><span class="p">.</span><span class="nf">length</span><span class="p">(</span><span class="n">text</span><span class="p">)</span> <span class="o">==</span> <span class="m">2</span>
  <span class="k">let</span> <span class="n">bytes</span> <span class="o">=</span> <span class="n">bit_array</span><span class="p">.</span><span class="nf">from_string</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
  <span class="k">assert</span> <span class="n">bit_array</span><span class="p">.</span><span class="nf">byte_size</span><span class="p">(</span><span class="n">bytes</span><span class="p">)</span> <span class="o">==</span> <span class="m">6</span>
  <span class="k">assert</span> <span class="n">bit_array</span><span class="p">.</span><span class="nf">to_string</span><span class="p">(</span><span class="n">bytes</span><span class="p">)</span> <span class="o">==</span> <span class="nc">Ok</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The rabbit occupies four UTF-8 bytes. The round trip checks that converting to bytes and back preserves the original text rather than merely producing something printable.</p>

<h2 id="do-not-turn-a-small-example-into-a-universal-rule">Do not turn a small example into a universal rule</h2>

<p>These examples avoid another distinction: a visible character can be made from several Unicode code points. Combining marks and emoji sequences mean that counting code points is not always counting the units a reader perceives on screen.</p>

<p>In this library, <code class="language-plaintext highlighter-rouge">string.length</code> counts Unicode code points. A combining mark therefore counts separately:</p>

<div class="language-pooka highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">pooka</span><span class="o">/</span><span class="n">string</span>

<span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">assert</span> <span class="n">string</span><span class="p">.</span><span class="nf">length</span><span class="p">(</span><span class="s2">"e</span><span class="se">\u{301}</span><span class="s2">"</span><span class="p">)</span> <span class="o">==</span> <span class="m">2</span>
  <span class="k">assert</span> <span class="n">string</span><span class="p">.</span><span class="nf">length</span><span class="p">(</span><span class="s2">"é"</span><span class="p">)</span> <span class="o">==</span> <span class="m">1</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The two spellings can look like the same accented letter while having different code-point sequences. A byte-oriented slice also needs to consider what happens when its boundary lands inside an encoded character.</p>

<p>This is relevant to the compiler itself. Source spans can use byte offsets internally, while diagnostics need to place underlines in displayed source. Confusing the measurements points the complaint at an innocent neighbour.</p>

<p>The rulers are useful. Trouble begins when one is handed to every caller without asking what they were trying to measure.</p>

<p>— Peregrine Finch</p>]]></content><author><name>Peregrine Finch</name></author><category term="pooka" /><category term="unicode" /><category term="stdlib" /><summary type="html"><![CDATA[UTF-8 bytes, code points and visible text are related measurements with different jobs.]]></summary></entry><entry><title type="html">The error takes the next train</title><link href="/2026/10/04/the-error-takes-the-next-train/" rel="alternate" type="text/html" title="The error takes the next train" /><published>2026-10-04T00:00:00+00:00</published><updated>2026-10-04T00:00:00+00:00</updated><id>/2026/10/04/the-error-takes-the-next-train</id><content type="html" xml:base="/2026/10/04/the-error-takes-the-next-train/"><![CDATA[<p>An error has arrived at a function carrying a small overnight bag. We can inspect it, return it, or insist it should never have been allowed on the train.</p>

<h2 id="let-a-recoverable-error-return">Let a recoverable error return</h2>

<p>These are different decisions.</p>

<div class="language-pooka highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">pooka</span><span class="o">/</span><span class="n">int</span>

<span class="nf">sum</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nc">Result</span><span class="p">(</span><span class="nc">Int</span><span class="p">,</span> <span class="n">int</span><span class="p">.</span><span class="nc">ParseError</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">let</span> <span class="n">first</span> <span class="o">=</span> <span class="n">int</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">a</span><span class="p">)</span><span class="o">?</span>
  <span class="k">let</span> <span class="n">second</span> <span class="o">=</span> <span class="n">int</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">b</span><span class="p">)</span><span class="o">?</span>
  <span class="nc">Ok</span><span class="p">(</span><span class="n">first</span> <span class="o">+</span> <span class="n">second</span><span class="p">)</span>
<span class="p">}</span>

<span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">assert</span> <span class="nf">sum</span><span class="p">(</span><span class="s2">"20"</span><span class="p">,</span> <span class="s2">"22"</span><span class="p">)</span> <span class="o">==</span> <span class="nc">Ok</span><span class="p">(</span><span class="m">42</span><span class="p">)</span>
  <span class="k">assert</span> <span class="nf">sum</span><span class="p">(</span><span class="s2">"20"</span><span class="p">,</span> <span class="s2">"owl"</span><span class="p">)</span> <span class="o">==</span> <span class="nc">Error</span><span class="p">(</span><span class="n">int</span><span class="p">.</span><span class="nc">ParseError</span><span class="p">(</span><span class="s2">"owl"</span><span class="p">))</span>
  <span class="k">let</span> <span class="k">assert</span> <span class="nc">Ok</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">=</span> <span class="nf">sum</span><span class="p">(</span><span class="s2">"20"</span><span class="p">,</span> <span class="s2">"22"</span><span class="p">)</span>
  <span class="k">assert</span> <span class="n">value</span> <span class="o">==</span> <span class="m">42</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="unfold-the-punctuation">Unfold the punctuation</h2>

<p>Here is the propagation written out in full:</p>

<div class="language-pooka highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">pooka</span><span class="o">/</span><span class="n">int</span>

<span class="nf">sum</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nc">Result</span><span class="p">(</span><span class="nc">Int</span><span class="p">,</span> <span class="n">int</span><span class="p">.</span><span class="nc">ParseError</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">match</span> <span class="n">int</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="p">{</span>
    <span class="nc">Error</span><span class="p">(</span><span class="n">reason</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nc">Error</span><span class="p">(</span><span class="n">reason</span><span class="p">)</span>
    <span class="nc">Ok</span><span class="p">(</span><span class="n">first</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">match</span> <span class="n">int</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="p">{</span>
      <span class="nc">Error</span><span class="p">(</span><span class="n">reason</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nc">Error</span><span class="p">(</span><span class="n">reason</span><span class="p">)</span>
      <span class="nc">Ok</span><span class="p">(</span><span class="n">second</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nc">Ok</span><span class="p">(</span><span class="n">first</span> <span class="o">+</span> <span class="n">second</span><span class="p">)</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">assert</span> <span class="nf">sum</span><span class="p">(</span><span class="s2">"20"</span><span class="p">,</span> <span class="s2">"22"</span><span class="p">)</span> <span class="o">==</span> <span class="nc">Ok</span><span class="p">(</span><span class="m">42</span><span class="p">)</span>
  <span class="k">assert</span> <span class="nf">sum</span><span class="p">(</span><span class="s2">"20"</span><span class="p">,</span> <span class="s2">"owl"</span><span class="p">)</span> <span class="o">==</span> <span class="nc">Error</span><span class="p">(</span><span class="n">int</span><span class="p">.</span><span class="nc">ParseError</span><span class="p">(</span><span class="s2">"owl"</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The question mark opens a <code class="language-plaintext highlighter-rouge">Result</code>. With <code class="language-plaintext highlighter-rouge">Ok</code>, execution continues using its contents. With <code class="language-plaintext highlighter-rouge">Error</code>, the surrounding function returns that error. The second parse does not run if the first one has already failed.</p>

<p>The function must itself return a compatible <code class="language-plaintext highlighter-rouge">Result</code>. Otherwise there would be nowhere honest to send the error.</p>

<h2 id="an-expectation-is-a-stronger-promise">An expectation is a stronger promise</h2>

<p><code class="language-plaintext highlighter-rouge">let assert</code> makes a stronger statement. It requires the pattern to match. If it does not, execution fails instead of returning the unmatched value as an ordinary error result.</p>

<p>For a fixed example string in a test, that expectation can be useful. For a stranger’s configuration file, it may be entirely the wrong hospitality. Invalid user input is usually a possibility the application should explain, rather than an impossible event it should crash over.</p>

<p>Neither spelling should hide repeated evaluation in generated code. If the expression reads a line or sends a request, evaluating it twice changes the program. Lowering the convenient syntax must preserve both the result and the number of times the work happens.</p>

<p>I like these small features when their meaning stays visible. <code class="language-plaintext highlighter-rouge">?</code> passes a recoverable result back to the caller. <code class="language-plaintext highlighter-rouge">let assert</code> draws a line around an expectation the program will not continue without.</p>

<p>The punctuation is brief. The choice it records is not.</p>

<p>— Peregrine Finch</p>]]></content><author><name>Peregrine Finch</name></author><category term="pooka" /><category term="errors" /><category term="types" /><summary type="html"><![CDATA[Result propagation and let assert look convenient for different reasons, and should fail differently too.]]></summary></entry><entry><title type="html">A process keeps its own weather</title><link href="/2026/09/30/a-process-keeps-its-own-weather/" rel="alternate" type="text/html" title="A process keeps its own weather" /><published>2026-09-30T00:00:00+00:00</published><updated>2026-09-30T00:00:00+00:00</updated><id>/2026/09/30/a-process-keeps-its-own-weather</id><content type="html" xml:base="/2026/09/30/a-process-keeps-its-own-weather/"><![CDATA[<p>A process is a small place where something can happen without the whole program happening to it at once.</p>

<h2 id="give-the-state-an-address">Give the state an address</h2>

<p>The BEAM gives these places identities and mailboxes. Pooka gives one useful arrangement of them a language construct: the actor.</p>

<div class="language-pooka highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">actor</span> <span class="nc">Counter</span> <span class="p">{</span>
  <span class="n">var</span> <span class="n">count</span><span class="p">:</span> <span class="nc">Int</span> <span class="o">=</span> <span class="m">0</span>

  <span class="k">be</span> <span class="nf">add</span><span class="p">(</span><span class="n">amount</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">count</span> <span class="o">+=</span> <span class="n">amount</span>
  <span class="p">}</span>

  <span class="nf">value</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nc">Int</span> <span class="p">{</span>
    <span class="n">count</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">let</span> <span class="n">counter</span> <span class="o">=</span> <span class="nc">Counter</span><span class="p">()</span>
  <span class="n">counter</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="m">2</span><span class="p">)</span>
  <span class="n">counter</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="m">3</span><span class="p">)</span>
  <span class="k">assert</span> <span class="n">counter</span><span class="p">.</span><span class="nf">value</span><span class="p">()</span> <span class="o">==</span> <span class="m">5</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="a-message-and-a-question">A message and a question</h2>

<p>For this example’s single sender, the exchange looks like this:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>caller -&gt; add(2)  -&gt; counter
caller -&gt; add(3)  -&gt; counter
caller -&gt; value() -&gt; counter
caller &lt;- 5      &lt;- counter
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">be add</code> is a behaviour: calling it sends a message and returns without waiting for the state change. <code class="language-plaintext highlighter-rouge">value()</code> is a request that waits for an answer. The state belongs to the actor, and its handlers work through messages one at a time.</p>

<p>The example uses one sender. The two additions and the following request travel from that sender to the same actor in order. This is not a promise that messages from every process in the system share one universal arrival order.</p>

<p>That distinction matters. Concurrency explanations become rather too comfortable when they leave out the competing callers.</p>

<h2 id="decide-who-notices-a-failure">Decide who notices a failure</h2>

<p>An actor also is not automatically a complete fault-tolerance policy. The BEAM has links, monitors and supervision machinery, but an application still needs to decide who should notice a failure and what should happen next. A mailbox alone cannot make that decision kindly on our behalf.</p>

<p><a href="https://www.erlang.org/doc/system/ref_man_processes.html">Erlang’s process documentation</a> is worth reading alongside the language surface. The runtime beneath the actor has its own precise rules; a friendly keyword does not replace them.</p>

<p>What the boundary does give us is a place to put mutable state without making it everyone’s shared furniture. Ordinary class values remain immutable. The counter changes in the process that owns it.</p>

<p>There can be several kinds of weather in one running program. Each room should know which window is its own.</p>

<p>— Peregrine Finch</p>]]></content><author><name>Peregrine Finch</name></author><category term="pooka" /><category term="actors" /><category term="beam" /><summary type="html"><![CDATA[Actors put changing state behind a mailbox, where calls and messages have different jobs.]]></summary></entry></feed>