<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://martin.janiczek.cz/feed.xml" rel="self" type="application/atom+xml" /><link href="https://martin.janiczek.cz/" rel="alternate" type="text/html" /><updated>2026-08-02T18:00:01+00:00</updated><id>https://martin.janiczek.cz/feed.xml</id><title type="html">Martin Janiczek</title><subtitle></subtitle><author><name>Martin Janiczek</name></author><entry><title type="html">Improving Elm PRNG</title><link href="https://martin.janiczek.cz/2026/08/02/improving-elm-prng.html" rel="alternate" type="text/html" title="Improving Elm PRNG" /><published>2026-08-02T00:00:00+00:00</published><updated>2026-08-02T00:00:00+00:00</updated><id>https://martin.janiczek.cz/2026/08/02/improving-elm-prng</id><content type="html" xml:base="https://martin.janiczek.cz/2026/08/02/improving-elm-prng.html"><![CDATA[<p><em>A bug is uncovered in the de facto PRNG library in the Elm language ecosystem
and alternative PRNG algorithms are suggested.</em></p>

<p>I’m not completely sure where I stumbled upon it, but I watched the <a href="https://www.youtube.com/watch?v=LWFzPP8ZbdU">Noise-based
RNG</a> GDC talk this Saturday morning.</p>

<p>In it Squirrel Eiserloh makes a case for using stateless hash functions for
procedurally generated content.</p>

<p>There are some new capabilities this unlocks: the poster child example is
procedurally generated terrain in a game like Minecraft: each 2D coordinate can
have a stable terrain generated regardless of the player actions so far.</p>

<p>Later in the talk Squirrel asks the question, can we generate a PRNG out of a
hash function?</p>

<p>The answer is yes: you wrap the hash function (<code>hash(value: Int): Int</code>) into a
stateful wrapper <code>rng(): Int</code> which increments the hidden <code>value</code> everytime it’s
called. Since hash functions possess the <em>avalanche effect</em> (tiny change in
input causes a large change in the output), <code>hash(0)</code> should be wildly different
from and unrelated to <code>hash(1)</code>.</p>

<p>You end up with something like:</p>

<pre><code class="language-c">// What you originally had
uint32_t hash(uint32_t value) {
  return do_something_to(value);
}

// PRNG wrapper
static uint32_t position = 0;
uint32_t rng_next() {
    return hash(position++);
}
</code></pre>

<p>where a classic PRNG like Mersenne Twister or XorShift would look more like:</p>

<pre><code class="language-c">static state_t state = /* snip */;
uint32_t rng_next() {
    uint32_t value = read_value_from(state);
    advance(&amp;state);
    return value;
}
</code></pre>

<p>Squirrel then claims the hash function based RNGs are more performant than the
LCG ones in his measurements, which elevated the talk from a neat idea to “let’s
test it out!”</p>

<p>Specifically, I want to implement Squirrel’s suggested hash function in
<a href="https://elm-lang.org">Elm</a> and compare it to the “official” Elm PRNG in terms
of performance and randomness test batteries like
<a href="https://webhome.phy.duke.edu/~rgb/General/dieharder.php">Dieharder</a> and
<a href="https://www.iro.umontreal.ca/~simardr/testu01/tu01.html">TestU01</a>.</p>

<p>All relevant code can be found in the repo <a href="https://github.com/Janiczek/elm-prng-20260802">Janiczek/elm-prng-20260802</a>.</p>

<h2 id="squirrels-hash-function">Squirrel’s hash function</h2>

<p>In the GDC talk Squirrel gives his hash function on a slide:</p>

<pre><code class="language-cpp">unsigned int Squirrel3( int positionX, unsigned int seed )
{
    const unsigned int BIT_NOISE1 = 0x68E31DA4; // 0b0110'1000'1110'0011'0001'1101'1010'0100;
    const unsigned int BIT_NOISE2 = 0xB5297A4D; // 0b1011'0101'0010'1001'0111'1010'0100'1101;
    const unsigned int BIT_NOISE3 = 0x1B56C4E9; // 0b0001'1011'0101'0110'1100'0100'1110'1001;

    unsigned int mangledBits = (unsigned int) positionX;
    mangledBits *= BIT_NOISE1;
    mangledBits += seed;
    mangledBits ^= (mangledBits &gt;&gt; 8);
    mangledBits += BIT_NOISE2;
    mangledBits ^= (mangledBits &lt;&lt; 8);
    mangledBits *= BIT_NOISE3;
    mangledBits ^= (mangledBits &gt;&gt; 8);
    return mangledBits;
}
</code></pre>

<p>Originally I wanted to port it directly, but then I <a href="https://x.com/SquirrelTweets/status/1421251894274625536">found
out</a> he has published
<a href="http://eiserloh.net/noise/SquirrelNoise5.hpp">an updated version</a> that should
behave better. So let’s use that one.</p>

<p>Here is the relevant part:</p>
<pre><code class="language-cpp">constexpr unsigned int SquirrelNoise5( int positionX, unsigned int seed )
{
    constexpr unsigned int SQ5_BIT_NOISE1 = 0xD2A80A3F; // 11010010101010000000101000111111
    constexpr unsigned int SQ5_BIT_NOISE2 = 0xA884F197; // 10101000100001001111000110010111
    constexpr unsigned int SQ5_BIT_NOISE3 = 0x6C736F4B; // 01101100011100110110111101001011
    constexpr unsigned int SQ5_BIT_NOISE4 = 0xB79F3ABB; // 10110111100111110011101010111011
    constexpr unsigned int SQ5_BIT_NOISE5 = 0x1B56C4F5; // 00011011010101101100010011110101

    unsigned int mangledBits = (unsigned int) positionX;
    mangledBits *= SQ5_BIT_NOISE1;
    mangledBits += seed;
    mangledBits ^= (mangledBits &gt;&gt; 9);
    mangledBits += SQ5_BIT_NOISE2;
    mangledBits ^= (mangledBits &gt;&gt; 11);
    mangledBits *= SQ5_BIT_NOISE3;
    mangledBits ^= (mangledBits &gt;&gt; 13);
    mangledBits += SQ5_BIT_NOISE4;
    mangledBits ^= (mangledBits &gt;&gt; 15);
    mangledBits *= SQ5_BIT_NOISE5;
    mangledBits ^= (mangledBits &gt;&gt; 17);
    return mangledBits;
}
</code></pre>

<p>Repeating Squirrel’s point from the talk: to use this hash function as a PRNG,
you’d pick a <code>seed</code> at the beginning, set <code>position</code> to 0 and increment it
whenever the hash function was used. You can have multiple unrelated PRNGs by
picking different seeds (or eg. hashing a game entity ID to be the initial
seed).</p>

<p>I have ported it to Elm; the hash function converted to a PRNG can be found in
<a href="https://github.com/Janiczek/elm-prng-20260802/blob/main/src/Random/Squirrel5.elm"><code>src/Random/Squirrel5.elm</code></a>.
It’s of form <code>State -&gt; (Int, State)</code> instead of <code>() -&gt; Int</code> because Elm is pure
and can’t hide side effects.</p>

<p>The numbers given by the Elm implementation agree with the C code’s output; same
with all other implementations below.</p>

<h2 id="jkiss32">JKISS32</h2>

<p>I can’t recall which rabbit hole led me there, but somehow when reading about
Dieharder etc. I have opened the paper <a href="http://www0.cs.ucl.ac.uk/staff/d.jones/GoodPracticeRNG.pdf">“Good Practice in (Pseudo) Random Number
Generation for Bioinformatics
Applications”</a>.</p>

<p>In it the author gives a few examples of their recommended PRNGs. Most are
64bit, but there is one working on 32bit numbers and only uses additions and
shifts (no multiplications!), so let’s try it - it might be a lot faster than
the multiplication-using ones? You never know.</p>

<blockquote>
  <p>Note: Why 32bit numbers? Elm doesn’t have access to (fast) 64bit integers, as
  it is compiled to JavaScript and JavaScript numbers are doubles (binary64 IEEE
  754 floats). The <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER">maximum safe
  integer</a>
  is <code>2^53 - 1</code>, so we can’t quite reach 64bit integers.</p>

  <p>Above that number, the doubles backing the JavaScript numbers get far enough
  from each other that <code>9007199254740991 + 1 === 9007199254740991 + 2</code>.</p>
</blockquote>

<p>The paper gives the following C code for the 32bit addition-only PRNG:</p>

<pre><code class="language-c">static unsigned int x=123456789,y=234567891,z=345678912,w=456789123,c=0;
unsigned int JKISS32()
{
    int t;
    y ^= (y&lt;&lt;5); y ^= (y&gt;&gt;7); y ^= (y&lt;&lt;22);
    t = z+w+c; z = w; c = t &lt; 0; w = t&amp;2147483647;
    x += 1411392427;
    return x + y + w;
}
</code></pre>

<p>My Elm implementation can be found in
<a href="https://github.com/Janiczek/elm-prng-20260802/blob/main/src/Random/JKISS32.elm"><code>src/Random/JKISS32.elm</code></a>.</p>

<h2 id="elm-pcg---the-status-quo">Elm PCG - the status quo</h2>

<p>There is a single “blessed” Elm random number generator library:
<a href="https://package.elm-lang.org/packages/elm/random/latest/"><code>elm/random</code></a>.</p>

<blockquote>
  <p>There are multiple alternative PRNGs published on <a href="https://package.elm-lang.org/">package.elm-lang.org</a> but we’ll ignore them in this blogpost.</p>
</blockquote>

<p>At least as of <a href="https://github.com/elm/random/blob/1.0.0/src/Random.elm">version
<code>1.0.0</code></a>, <code>elm/random</code>
uses a variant of <a href="https://www.pcg-random.org/">PCG</a> <em>(Permuted Congruential
Generator)</em>, specifically the <a href="https://github.com/imneme/pcg-c/blob/83252d9c23df9c82ecb42210afed61a7b42402d7/include/pcg_variants.h#L182-L186"><code>RXS-M-XS</code>
version</a>
(check section 6.3.4 in the <a href="https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf">PCG
paper</a>) with some custom
decisions on how to initialize the RNG with just a single 32bit number instead
of two.</p>

<p>If code-golfed to a minimal C code, the Elm’s algorithm could look like this:</p>

<pre><code class="language-c">typedef struct { uint32_t state, incr; } elm_seed_t;

static inline void elm_next(elm_seed_t *s)
{
    double product = (double)s-&gt;state * 1664525.0; // Spoilers...
    uint64_t truncated = (uint64_t)product;
    uint32_t word = (uint32_t)(truncated &amp; 0xFFFFFFFFu);
    s-&gt;state = word + s-&gt;incr;
}

static inline uint32_t elm_peel(uint32_t state)
{
    uint32_t xored = state ^ (state &gt;&gt; ((state &gt;&gt; 28u) + 4u));
    double product = (double)xored * 277803737.0; // Spoilers...
    uint64_t truncated = (uint64_t)product;
    uint32_t word = (uint32_t)(truncated &amp; 0xFFFFFFFFu);
    return (word &gt;&gt; 22u) ^ word;
}

static void elm_initial_seed(elm_seed_t *s, uint32_t x)
{
    elm_seed_t tmp = { 0u, 1013904223u };
    elm_next(&amp;tmp);
    tmp.state += x;
    elm_next(&amp;tmp);
    *s = tmp;
}

static inline uint32_t elm_step_random_r(elm_seed_t *s)
{
    uint32_t out = elm_peel(s-&gt;state);
    elm_next(s);
    return out;
}
</code></pre>

<blockquote>
  <p>There is a whole combinator library sitting on top of the <code>next</code> and <code>peel</code>
  primitives, doing some interesting bias-correcting math for returning random
  numbers in a certain range, but I’ll consider these out of scope. <code>peel</code> gives
  the “raw” bits that everything else is made of, and that will be what we
  compare against the other algorithms.</p>
</blockquote>

<p>You might have seen the comment and the surprisingly complex code in the <code>next</code>
and <code>peel</code> functions above. Doubles?! This is to emulate JavaScript behaviour:
the Elm code is slightly buggy because of multiplying two 32bit ints in a naive
way.</p>

<p>Multiplying two 32bit integers can produce a 64bit integer, and as mentioned
above we don’t have those in the JavaScript world. The numbers we get back don’t
fall on the nearest integer to the true result but <em>get rounded</em> to some
multiple of <code>2^n</code> depending on how large the result is.</p>

<p>The loss of precision means the current implementation of the PCG algorithm
follows a slightly different trajectory for its internal state and the generated
numbers than intended (than the canonical PCG algorithm would use for the given
seed). I don’t know enough about these to <em>explain</em> how bad it is, but as we’ll
see from the Dieharder results later, it <em>does</em> have an adverse effect on the
randomness of the generated numbers.</p>

<p>JavaScript has a workaround for the 32bit multiplication:
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul"><code>Math.imul()</code></a>.
We don’t have access to it in the Elm userland, instead we need to emulate it
with bitwise operations like <code>&amp;</code> and <code>&gt;&gt;&gt;</code> (see <code>imul32</code> in
<a href="https://github.com/Janiczek/elm-prng-20260802/blob/main/src/Bitwise/Extra.elm"><code>src/Bitwise/Extra.elm</code></a>),
resulting in compiled JS code similar to this:</p>

<pre><code class="language-typescript">function imul32(a,b) {
    var aLo = a &amp; 0xFFFF;
    var bLo = b &amp; 0xFFFF;
    var low = aLo * bLo;
    var aHi = a &gt;&gt;&gt; 16;
    var bHi = b &gt;&gt;&gt; 16;
    var high = ((aHi * bLo) + (aLo * bHi)) &amp; 0xFFFF;
    return (low + (high * 0x10000)) &gt;&gt;&gt; 0;
}
</code></pre>

<p>I only realized the bug is present when I ported the Elm PRNG to C and found it
doesn’t give me the same numbers as the Elm version. Sure enough, it was that
multiplication! When stepping through both implementations and comparing the
numbers, I saw Elm give intermediate numbers like <code>5145799907274839000</code>: way too
big, and the <code>000</code> at the end was suspicious. <em>(I see this routinely at work
when webapps try to show big integers coming from Snowflake. Gotta love
JavaScript.)</em></p>

<p>Changing the raw multiplications into an emulated <code>imul32</code> fixed the issue. I
have made a PR for <code>elm/random</code> (as we’ll see later, the gradual loss of
precision does make a difference in the statistical tests) and continued with
the exploration; though I expect the correct fix will be to rather add a new
<code>Bitwise.imul</code> primitive to <code>elm/core</code> that will call <code>Math.imul()</code>, and use it
from <code>elm/random</code> instead of the emulated version.</p>

<p>Here is how you probably expected the PCG C code from above to look:</p>

<pre><code class="language-diff"> static inline void elm_next(elm_seed_t *s)
 {
-    double product = (double)s-&gt;state * 1664525.0; // Spoilers...
-    uint64_t truncated = (uint64_t)product;
-    uint32_t word = (uint32_t)(truncated &amp; 0xFFFFFFFFu);
-    s-&gt;state = word + s-&gt;incr;
+    s-&gt;state = s-&gt;state * 1664525u + s-&gt;incr;
 }
 
 static inline uint32_t elm_peel(uint32_t state)
 {
-    uint32_t xored = state ^ (state &gt;&gt; ((state &gt;&gt; 28u) + 4u));
-    double product = (double)xored * 277803737.0; // Spoilers...
-    uint64_t truncated = (uint64_t)product;
-    uint32_t word = (uint32_t)(truncated &amp; 0xFFFFFFFFu);
+    uint32_t word = (state ^ (state &gt;&gt; ((state &gt;&gt; 28u) + 4u))) * 277803737u;
     return (word &gt;&gt; 22u) ^ word;
 }
</code></pre>

<p>That is, use integer multiplication instead of doubles.</p>

<p>We’ll add two fixed PCG variants to the test suite and benchmarks:</p>

<ol>
  <li>One that uses the emulated <code>imul32</code> (expressible in “userspace” 3rd party Elm
packages)</li>
  <li>One that uses <code>Math.imul()</code>.</li>
</ol>

<p>OK, enough talk about our contenders, let’s whip out the test batteries and see
how random each implementation really is!</p>

<h2 id="dieharder">Dieharder</h2>

<p>My initial attempt was to let Elm print a large amount of random numbers into a
file, which I’d then pass to Dieharder with <code>-g 202</code>.</p>

<p>That’s not such a good idea: the tests need you to generate <em>A LOT</em> of
randomness. 2^30 numbers (roughly 11GB in filesize) is not enough and taints the
results. Dieharder automatically rewinds the file and sees the same numbers
repeated, which causes some tests to fail.</p>

<p>Another approach was necessary. Dieharder has a mode <code>-g 200</code> which consumes raw
binary data from <code>STDIN</code>. That plus the fact that I wasn’t too impressed with
the speed at which Elm printed the numbers, led me to use the C versions of the
algorithms and port the Elm algorithm to C.</p>

<p>In the end I’m running Dieharder with the following flags:</p>

<pre><code>$ ./jkiss32 | dieharder -k 2 -Y 1 -a -g 200
#                       ^         ^  ^^^^^^ load input from pipe
#                       ^         ^^ run all tests
#                       ^^^^^^^^^
#                       "Reduce Ambiguity" mode:
#           if WEAK, run more tests to end up at PASSED / FAILED
</code></pre>

<p>Full Dieharder reports are <a href="https://github.com/Janiczek/elm-prng-20260802/tree/main/out/dieharder">in the
repository</a>;
here is the summary:</p>

<table>
  <thead>
    <tr>
      <th>Algorithm</th>
      <th style="text-align: right">Passed</th>
      <th style="text-align: right">Weak</th>
      <th>Failed</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Elm PCG (current, buggy)</td>
      <td style="text-align: right">91</td>
      <td style="text-align: right">7</td>
      <td>16</td>
    </tr>
    <tr>
      <td>Elm PCG (fixed)</td>
      <td style="text-align: right">114</td>
      <td style="text-align: right">0</td>
      <td>0</td>
    </tr>
    <tr>
      <td>SquirrelNoise5</td>
      <td style="text-align: right">114</td>
      <td style="text-align: right">0</td>
      <td>0</td>
    </tr>
    <tr>
      <td>JKISS32</td>
      <td style="text-align: right">114</td>
      <td style="text-align: right">0</td>
      <td>0</td>
    </tr>
  </tbody>
</table>

<p>I don’t understand why Dieharder ignored some of the <code>WEAK</code> results, but the
picture is pretty clear anyways: the current <code>elm/random</code> PRNG is statistically
flawed due to the double multiplication bug. The other PRNGs don’t seem to have
any apparent issues.</p>

<p>More testing is needed. Introducing: <a href="https://en.wikipedia.org/wiki/TestU01">TestU01</a>.</p>

<h2 id="testu01">TestU01</h2>

<p>I ran the BigCrush test battery from TestU01 on all four algorithms <a href="https://github.com/Janiczek/elm-prng-20260802/tree/main/out/testu01_bigcrush">(see the
reports)</a>.</p>

<table>
  <thead>
    <tr>
      <th>Algorithm</th>
      <th style="text-align: right">Failed tests</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Elm PCG (current, buggy)</td>
      <td style="text-align: right">12</td>
    </tr>
    <tr>
      <td>Elm PCG (fixed)</td>
      <td style="text-align: right">9</td>
    </tr>
    <tr>
      <td>SquirrelNoise5</td>
      <td style="text-align: right">8</td>
    </tr>
    <tr>
      <td>JKISS32</td>
      <td style="text-align: right"><strong>0</strong></td>
    </tr>
  </tbody>
</table>

<p>A very surprising result!</p>

<p>First of all, given PCG’s reputation I’d expect it to pass. Is it possible the
Elm implementation differs enough from the official one that it screws something
up? Another experiment using the official PCG algorithm and constants would be
needed.</p>

<p>Secondly, JKISS32 passes with flying colors. At least that gives us a clear
favorite when we’ll next look at performance.</p>

<p>I don’t really know what the failures mean in practice. The algorithms above
<em>aren’t</em> cryptographically secure, so I don’t suppose any of them is safe from
predicting new values from previous ones etc., but maybe there could be some
bias that could realistically affect users in web app / game / simulation
scenarios?</p>

<p>Anyways, being naive, let’s bias towards JKISS32 and go see the performance.</p>

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

<p>I want to measure how long it takes to generate a random number and advance the
state <em>once</em>, and <em>1 million times</em> in a typical Elm loop (that is, a tail
recursive function).</p>

<p>For benchmarking the Elm code I have compiled (with <code>elm make --optimize</code>) a
test program like this just to get the final JavaScript out:</p>

<pre><code class="language-elm">import Random.CoreCurrent
import Random.CoreFixed
import Random.JKISS32
import Random.Squirrel5

coreCurrentState = Random.CoreCurrent.initialState 123456789
coreFixedState   = Random.CoreFixed.initialState   123456789
jkissState       = Random.JKISS32.initialState     123456789 234567891 345678912 456789123
squirrelState    = Random.Squirrel5.initialState   123456789

runTimes : Int -&gt; (state -&gt; ( Int, state )) -&gt; state -&gt; ()
runTimes n step state =
    if n &lt;= 0 then
        ()

    else
        let
            ( generatedValue, newState ) =
                step state
        in
        runTimes (n - 1) step newState

runCoreCurrent_1 () = let _ = Random.CoreCurrent.step coreCurrentState in ()
runCoreFixed_1   () = let _ = Random.CoreFixed.step   coreFixedState   in ()
runJKISS32_1     () = let _ = Random.JKISS32.step     jkissState       in ()
runSquirrel5_1   () = let _ = Random.Squirrel5.step   squirrelState    in ()

runCoreCurrent_N n = runTimes n Random.CoreCurrent.step coreCurrentState
runCoreFixed_N   n = runTimes n Random.CoreFixed.step   coreFixedState
runJKISS32_N     n = runTimes n Random.JKISS32.step     jkissState
runSquirrel5_N   n = runTimes n Random.Squirrel5.step   squirrelState

main =
    let
        -- Just to prevent them from being dead-code-eliminated.
        _ = runCoreCurrent_1 ()
        _ = runCoreFixed_1   ()
        _ = runJKISS32_1     ()
        _ = runSquirrel5_1   ()
        _ = runCoreCurrent_N 1
        _ = runCoreFixed_N   1
        _ = runJKISS32_N     1
        _ = runSquirrel5_N   1
    in
    -- A necessary boilerplate to compile an Elm program
    Platform.worker
        { init = \() -&gt; ( (), Cmd.none )
        , update = \_ _ -&gt; ( (), Cmd.none )
        , subscriptions = \_ -&gt; Sub.none
        }
</code></pre>

<p>Then I hand-picked the relevant parts into <a href="https://github.com/Janiczek/elm-prng-20260802/blob/main/mitata-benchmark.mjs">a JavaScript
file</a>
and used <a href="https://github.com/evanwashere/mitata"><code>mitata</code></a> to benchmark it.</p>

<p>I kept the loops and function calls Elm-style instead of idiomatic JavaScript,
to measure the realistic Elm behaviour one can reach.</p>

<p>Here are the results. I’m striking through the current <code>elm/random</code> code because
while it’s fastest, I’d prefer correctness over performance.</p>

<table>
  <thead>
    <tr>
      <th>Algorithm</th>
      <th style="text-align: right">1x [ns]</th>
      <th style="text-align: right">1 000 000x [ms]</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>elm/random</code> PCG (current, buggy)</td>
      <td style="text-align: right"><del>2.00</del></td>
      <td style="text-align: right"><del>10.62</del></td>
    </tr>
    <tr>
      <td><code>elm/random</code> PCG (fixed, userspace)</td>
      <td style="text-align: right">7.05</td>
      <td style="text-align: right">19.07</td>
    </tr>
    <tr>
      <td><code>elm/random</code> PCG (fixed, <code>Math.imul</code>)</td>
      <td style="text-align: right"><strong>1.94</strong></td>
      <td style="text-align: right">16.45</td>
    </tr>
    <tr>
      <td>JKISS32</td>
      <td style="text-align: right">3.42</td>
      <td style="text-align: right">21.88</td>
    </tr>
    <tr>
      <td>SquirrelNoise5</td>
      <td style="text-align: right">5.54</td>
      <td style="text-align: right"><strong>12.22</strong></td>
    </tr>
  </tbody>
</table>

<p>Box plots reported by Mitata:</p>

<p><span class="next-two-code-blocks-have-box-drawing"></span></p>

<pre><code class="language-plaintext">1x

                   ┌                                            ┐
                   ╷┬
       coreCurrent ├│
                   ╵┴
                                           ╷┌─┬                 ╷
coreFixedUserspace                         ├┤ │─────────────────┤
                                           ╵└─┴                 ╵
                   ┌┬
   coreFixedNative ││
                   └┴
                          ╷┬            ╷
           jkiss32        ├│────────────┤
                          ╵┴            ╵
                                     ┌┬             ╷
         squirrel5                   ││─────────────┤
                                     └┴             ╵
                   └                                            ┘
                   1.83 ns            6.21 ns            10.59 ns
</code></pre>

<pre><code class="language-plaintext">1000000x

                   ┌                                            ┐
                   ╷┬╷
       coreCurrent ├│┤
                   ╵┴╵
                                                   ┌┬╷
coreFixedUserspace                                 ││┤
                                                   └┴╵
                                         ┌┬┐╷
   coreFixedNative                       ││├┤
                                         └┴┘╵
                                                              ┌┬╷
           jkiss32                                            ││┤
                                                              └┴╵
                         ╷┬╷
         squirrel5       ├│┤
                         ╵┴╵
                   └                                            ┘
                   10.43 ms           16.30 ms           22.18 ms
</code></pre>

<p>The current <code>elm/random</code> algorithm is really fast compared to all the others. I
think it also must have to do with lack of function calls (which in Elm-produced
JavaScript sometimes end up doing extra logic around arity due to partial
application) in its implementation.</p>

<p>If I had to pick from the rest and <code>JKISS32</code>’s BigCrush test results weren’t
enough reason to pick it (or at least disqualify the rest and go search for
another function), <code>Squirrel5</code> seems to have great performance across multiple
runs although it’s not the fastest one in the “1x” test.</p>

<blockquote>
  <p>Note these numbers will surely be wildly different from their C counterparts:
  we’re operating in a GC’d interpreted language with a JIT…</p>
</blockquote>

<h2 id="final-results">Final results</h2>

<p>We learned there’s a bug in the <code>elm/random</code> PRNG implementation, causing it to
fail the Dieharder randomness test suite.</p>

<p>We learned that even fixing this bug doesn’t make it pass the TestU01 randomness
test suite, something we’d expect from the PCG family of PRNGs. Further
exploration of how the Elm implementation differs from the official one is
needed.</p>

<p>We learned about using hash functions as PRNGs; it can be surprisingly fast
(likely because virtually all the work is hidden in the generation, incrementing
an integer to advance the state is negligible).</p>

<p>This whole journey is probably not enough to conclusively say there is a clear winner, a function that (in context of JavaScript) provides the best performance while passing all the randomness tests. Maybe there is one out there, but we didn’t find it today.</p>

<h2 id="addendum-javascript-bitwise-tricks">Addendum: JavaScript bitwise tricks</h2>

<p>Here are JavaScript snippets for converting numbers to unsigned and signed 32bit
numbers with its bitwise operators, which you might be familiar with from
<a href="https://en.wikipedia.org/wiki/Asm.js">asm.js</a>:</p>

<pre><code class="language-typescript">const toU32 = (n) =&gt; n &gt;&gt;&gt; 0;
const toI32 = (n) =&gt; n | 0;

// 0xFFFFFFFF     == 4294967295
// 0xFFFFFFFF | 0 == -1
// -1 &gt;&gt;&gt; 0       == 4294967295

// 0x100000000       == 4294967296
// 0x100000000 | 0   == 0
// 0x100000000 &gt;&gt;&gt; 0 == 0
</code></pre>]]></content><author><name>Martin Janiczek</name></author><category term="Posts" /><summary type="html"><![CDATA[A bug is uncovered in the de facto PRNG library in the Elm language ecosystem and alternative PRNG algorithms are suggested.]]></summary></entry><entry><title type="html">Systems and Delays</title><link href="https://martin.janiczek.cz/2026/07/24/systems-and-delays.html" rel="alternate" type="text/html" title="Systems and Delays" /><published>2026-07-24T00:00:00+00:00</published><updated>2026-07-24T00:00:00+00:00</updated><id>https://martin.janiczek.cz/2026/07/24/systems-and-delays</id><content type="html" xml:base="https://martin.janiczek.cz/2026/07/24/systems-and-delays.html"><![CDATA[<p><em>where delays are shown to be counterintuitive.</em></p>

<p>Leaving the house (and the country) for a week or two during the holiday season is a great time to get out of my stereotypical ways of passing free time (entirely too much YouTube, Lobste.rs, HackerNews and BlueSky) and try something else.</p>

<p>For my recent vacation I have packed some grid paper to make a bitmap font, and two books: the first one was <a href="https://www.goldendog.cz/produkt/nachove-pustiny/"><em>Nachové pustiny</em></a>: a Czech post-apocalyptic novel to get some inspiration for my <a href="https://nuashworld.com">“Fallout but in Czech Republic” MMO game</a>, and the other was <a href="https://en.wikipedia.org/wiki/Thinking_In_Systems:_A_Primer"><em>Thinking in Systems</em></a>: a book recommended at work (and bought with my work education budget) which was collecting dust in my bookshelf for a good few months now.</p>

<p><a href="/assets/images/2026-07-24-systems-and-delays/books.webp"><img src="/assets/images/2026-07-24-systems-and-delays/books_thumbnail.webp" alt="Vacation stuff" /></a></p>

<p>I’m happy to announce I managed to get through all three of these goals: I’ve created a bitmap font (though I still have kerning and accents to go through), devoured the post-apo novel (it was okay), and got through the systems book!</p>

<p><em>Thinking in Systems</em> introduces a framework and a visual language of sources and sinks, stocks and flows, feedback loops and systems that (more often than not) emerge bottom-up from them.</p>

<p>It all roughly corresponds to mathematical models and differential equations, though the book doesn’t go into detail of those and only mentions the actual formulas for its examples in an appendix.</p>

<p><a href="/assets/images/2026-07-24-systems-and-delays/sources-and-sinks.png"><img src="/assets/images/2026-07-24-systems-and-delays/sources-and-sinks.png" alt="Sources and sinks" /></a></p>

<h2 id="delays-are-weird">Delays are weird</h2>

<p>There was one really cool chapter that IMHO is the highlight of the whole book. It concerns delays.</p>

<p>The running example (see image above) is a car dealership manager shepherding a stock of cars on their parking lot, aiming for it to always be 10x the amount of cars sold. When the customer demand rises, she starts ordering more cars to cover the gap.</p>

<p><a href="/assets/images/2026-07-24-systems-and-delays/typical-day.png"><img src="/assets/images/2026-07-24-systems-and-delays/typical-day.png" alt="Typical day" /></a></p>

<h3 id="delay-less-system">Delay-less system</h3>

<p>In an ideal world there are no delays: she immediately sees an increased demand and the discrepancy between the current stock and the ideal one, she immediately sends an order to bring more cars in, and the cars immediatelly arrive.</p>

<p>Also, in this example the time granularity is <em>days,</em> so imagine that at the end of the day she sees 30 cars got sold, sends an order for 30 cars and they arrive the next morning.</p>

<p>Let’s first see the constants in our model that won’t be affected by the delays:</p>

<p><a href="/assets/images/2026-07-24-systems-and-delays/constants.png"><img src="/assets/images/2026-07-24-systems-and-delays/constants.png" alt="Constants" /></a></p>

<p>The customer demand starts at 20 cars each day, then ramps up to 22 cars each day, with a spike of 70 cars on one specific day.</p>

<p>Sales are <code>min(customer demand, inventory)</code>, which in all examples below will equal <code>customer demand</code>, as we have enough cars ready, but you can imagine getting out of stock if demand gets high enough fast enough.</p>

<p>Our manager calculates the desired inventory as <code>sales * 10</code>. Might be simplistic but hey, it’s an example.</p>

<blockquote>
  <p>You could also imagine us later averaging over sales from past N days and basing the desired inventory off of that, and thus introducing yet another delay into the system, but I’ve omitted it from this blogpost as it doesn’t touch what I want to illustrate.</p>
</blockquote>

<p>So, how does the delay-less model behave?</p>

<p><a href="/assets/images/2026-07-24-systems-and-delays/0-1-no-delays.png"><img src="/assets/images/2026-07-24-systems-and-delays/0-1-no-delays.png" alt="No delays" /></a></p>

<blockquote>
  <p>BTW, I <a href="https://martin.janiczek.cz/thinking-in-systems-simulation"><em>have</em> written a Monte Carlo simulator</a> myself in Elm while reading the book—it lists the pseudocode formulas for the examples in this blogpost—but a spreadsheet would suffice, and there are other tools to deal with these differential equation models. Heck, <a href="https://insightmaker.com/insight/3kdrdgfu8mlQHIWEsqFjOy/Simple-Inventory-with-Delay-Model">there’s a free graphical webapp</a> built on top of a <a href="https://github.com/scottfr/simulation">JS library</a> showing off the same example I do in one of its examples. I guess the book <em>is</em> influential.</p>
</blockquote>

<p>This model has no delays, but it has an issue with overreacting to the random spikes in customer demand by buying too many cars which then take forever to sell, and <em>more importantly it’s unrealistic:</em> in the real world everything has a bit of a delay. The orders take time to process, the cars take time to arrive at the dealership lot, and so on. We’ll call that the <em>delivery delay.</em></p>

<p><a href="/assets/images/2026-07-24-systems-and-delays/delivery-delay.png"><img src="/assets/images/2026-07-24-systems-and-delays/delivery-delay.png" alt="Delivery delay" /></a></p>

<p>That seems like a bad delay, but delays don’t need to always be bad: consider that the manager might want to not overreact to those random spikes. She had ordered 550 cars because of a spike, when she normally orders only around 20 a day! Instead she might want to get to the desired inventory slowly over time by ordering only half of the discrepancy, or a third, etc. If on the next day things get back to normal, we’ll be able to ride the wave much more smoothly without overreacting. Let’s call this divisor the <em>response delay.</em></p>

<p><a href="/assets/images/2026-07-24-systems-and-delays/response-delay.png"><img src="/assets/images/2026-07-24-systems-and-delays/response-delay.png" alt="Response delay" /></a></p>

<h3 id="system-with-delays">System with delays</h3>

<p>Let’s run the model with these two delays and see:</p>

<ul>
  <li>delivery delay: 5 days (from me sending the order to the cars arriving on the lot)</li>
  <li>response delay divisor: 2 (if I see a discrepancy of 30 cars, I’ll only order 15 today.)</li>
</ul>

<p><a href="/assets/images/2026-07-24-systems-and-delays/5-2-realistic-delays.png"><img src="/assets/images/2026-07-24-systems-and-delays/5-2-realistic-delays.png" alt="Realistic delays" /></a></p>

<p>What? What just happened? We thought we’re doing everything right, the response delay was a good thing!</p>

<p>Well it is. But the combination of the two delays made our system oscillate! It never stabilizes even if the customer demand remains constant afterwards.</p>

<p>The manager is caught in a vile cycle of ordering too many cars, then ordering more and more the following days before the first order arrives. Once orders start arriving, she now has too many cars and ends up not ordering any more while waiting for the inventory to get back to the ideal levels. But then with more car sales the inventory dips below the ideal again and she starts the cycle again.</p>

<blockquote>
  <p>I’d be interested in learning what, mathematically, makes these oscillations inevitable. Some more math-heavy book on these models and differential equations will probably explain that. For now let’s just take the oscillations caused by delays for a fact.</p>
</blockquote>

<h3 id="shorter-delays-surely-the-fix">Shorter delays: surely the fix?</h3>

<p>Now you might think that since delays got us into this mess, we should minimize them as much as possible. Shorten the feedback loop and all. But remember, we introduced the response delay to be more resilient towards random spikes. Anyways, let’s try and see what happens:</p>

<ul>
  <li>delivery delay: 5 days (we can’t speed up this one)</li>
  <li>response delay divisor: 1 (be as fast as we can: buy the exact amount we’re missing at end of day)</li>
</ul>

<p><a href="/assets/images/2026-07-24-systems-and-delays/5-1-shorter-delays.png"><img src="/assets/images/2026-07-24-systems-and-delays/5-1-shorter-delays.png" alt="Shorter delays" /></a></p>

<p>Hm. As you can see, trying to be faster made things worse: our inventory of cars now oscillates much more: cycles of shooting up from 132 to 518, while we’d like to be around 220. Not to mention having to have 1160 cars in our lot at once after the spike! This is even worse than the no-delays scenario! Is there any hope?</p>

<h3 id="longer-delays-take-it-easy-dude">Longer delays: Take it easy, Dude…</h3>

<p>Just to see what will happen: let’s simulate a more patient manager: one that spreads out the restocking over a <em>longer</em> period of time. Let’s make our response delay divisor <em>larger</em> and try 6. When we see a discrepancy of 30 cars at the end of the day, let’s just order <em>5</em> instead of 10 or 15 or 30.</p>

<ul>
  <li>delivery delay: 5 days (still out of our control)</li>
  <li>response delay divisor: 6 (slower response! longer delay!)</li>
</ul>

<p><a href="/assets/images/2026-07-24-systems-and-delays/5-6-longer-delays.png"><img src="/assets/images/2026-07-24-systems-and-delays/5-6-longer-delays.png" alt="Longer delays" /></a></p>

<p>Wow! The oscillations died out and stabilized!</p>

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

<p>This was pretty counterintuitive to me when I read it. Delays intuitively seem like something you want to minimize. But here, shortening a delay made things worse and making it longer made things more predictable and less wasteful.</p>

<p>In the later parts of the book Donella Meadows mentions short feedback loops; there’s no clear cut rule that you could apply blindly. Make a model of your system, try different parameters, see how it behaves.</p>

<p>Many managers and leaders take the lever that the researchers provide, and crank it <em>full speed, wrong direction.</em> You might think you know which way is the correct one, but maybe, just maybe, get insight into how your system would behave in both directions and act on data instead of on intuition.</p>

<h3 id="ps">P.S.</h3>

<p>Feel free to play around with the <a href="https://martin.janiczek.cz/thinking-in-systems-simulation">toy simulator</a> for this example. The code is <a href="https://github.com/janiczek/thinking-in-systems-simulation">at Github</a>.</p>]]></content><author><name>Martin Janiczek</name></author><category term="Posts" /><summary type="html"><![CDATA[where delays are shown to be counterintuitive.]]></summary></entry><entry><title type="html">The LLM spectrum and responsible LLM use</title><link href="https://martin.janiczek.cz/2026/02/05/the-llm-spectrum-and-responsible-llm-use.html" rel="alternate" type="text/html" title="The LLM spectrum and responsible LLM use" /><published>2026-02-05T00:00:00+00:00</published><updated>2026-02-05T00:00:00+00:00</updated><id>https://martin.janiczek.cz/2026/02/05/the-llm-spectrum-and-responsible-llm-use</id><content type="html" xml:base="https://martin.janiczek.cz/2026/02/05/the-llm-spectrum-and-responsible-llm-use.html"><![CDATA[<p>In my mind there’s a spectrum going from <code>0.00</code> - “all manual” (where we all were
a few years ago) to <code>1.00</code> - “vibe coding” (you’re a product manager and don’t
look at any code at all, just spamming “XYZ doesn’t work for me, fix it”).</p>

<p>Obviously one is good enough but slow (or is it), and the other one is fast and
fun but not sustainable.</p>

<p>Is there a compromise in the middle that’s an improvement over not using LLMs
at all, and work-safe? (By work-safe I mean, you understand the code you
submitted in relation to the rest of the codebase, and the code meets some
quality bar.)</p>

<style>
.theme_fullscreen {
    display: flex;
    justify-content: center;
    padding: 0 1em 1em;
}
.theme_fullscreen img {
    width: calc(100dvw - 2em);
    max-width: 952px;
}
</style>

<div class="theme_fullscreen">
<a href="/assets/images/2026-02-05-the-llm-spectrum-and-responsible-llm-use/spectrum_1.png">
<img src="/assets/images/2026-02-05-the-llm-spectrum-and-responsible-llm-use/spectrum_1.png" alt="The spectrum (0.00, 1.00)" />
</a>
</div>

<p><em>(Note: the specific numbers are made up and the points don’t matter.)</em></p>

<h2 id="000-all-manual-no-llm-delegation"><code>0.00</code> (all manual, no LLM delegation)</h2>

<p>This is pretty self-explanatory. It’s how we all programmed before LLMs were a
thing. But this starting point is not the subject of this post - we want to
search the space for an usable point or range on it. So let’s go straight to
the other side.</p>

<h2 id="100-vibe-coding-pm-instructing-a-dev"><code>1.00</code> (vibe coding, PM instructing a dev)</h2>

<p>While it’s fun to experience this extreme – “write me an app that does XYZ” –
and eventually see your side project drive into a ditch, I don’t believe it’s
viable for anything long term (with current state-of-the-art models: Opus
4.5 as of the time of writing. <em>Yes I know, Opus 4.6 released today; I haven’t tried it yet.</em>).</p>

<p>You don’t know the codebase, tests are non-existent or bad or there’s not
enough of them… You’re just hoping adding a new feature or fixing a bug
didn’t break something else, and mostly rely on your own manual user-testing
for quality control.</p>

<p>I think it’s not controversial to mark this one as unsafe for work.</p>

<h2 id="070-dev-instructing-a-junior-dev"><code>0.70</code> (dev instructing a junior dev)</h2>

<div class="theme_fullscreen">
<a href="/assets/images/2026-02-05-the-llm-spectrum-and-responsible-llm-use/spectrum_2.png">
<img src="/assets/images/2026-02-05-the-llm-spectrum-and-responsible-llm-use/spectrum_2.png" alt="The spectrum (0.70)" />
</a>
</div>

<p>So, in a not quite binary-search style we go to the middle. (I’d mark this one
as <code>0.50</code> but then my crude Figma chart labels would overlap later. Let’s not speak
of it again.)</p>

<p>We’ve now dropped from high-level prompts to a lower level. We specify
technical details to the agent, outline algorithms or high level approaches,
ask for specific tests or write them ourselves; we skim the LLM code and read
tests carefully. We still write almost no code ourselves.</p>

<p>I think this is borderline usable for side projects with no real-world
importance and 0-1 users (you). The test suite does a lot, and quadruples the
project’s <code>TTAOAR</code> (<em>Time To Abandonment Or A Rewrite</em>).</p>

<p>This might be controversial, but I’ll say it’s not enough for serious work.
(See also the <a href="#others">Others?</a> section for extra nuance.)</p>

<h2 id="020-prompt-less-tab-autocomplete"><code>0.20</code> (prompt-less <code>Tab</code> autocomplete)</h2>

<div class="theme_fullscreen">
<a href="/assets/images/2026-02-05-the-llm-spectrum-and-responsible-llm-use/spectrum_3.png">
<img src="/assets/images/2026-02-05-the-llm-spectrum-and-responsible-llm-use/spectrum_3.png" alt="The spectrum (0.20)" />
</a>
</div>

<p>Instead of exploring the space from the right, let’s explore it from the safe
side for a moment.</p>

<p>I’d say that next to the <code>0.00</code> point, there’s the “Github Copilot” mode of
writing code. The “magic <code>Tab</code>” autocomplete in the editor.</p>

<p>You still are technically writing all the code, and the LLM is trying to finish
your sentences. When it guesses right, you press <code>Tab</code>. There’s never any
prompting from your side.</p>

<p>I think this is a pretty safe use of LLMs, and to me, it <em>feels</em> like a speed
up when writing the boring boilerplate parts of the code. Whether it actually
<em>is</em> a speed up, I don’t (want to) know.</p>

<p>Examples of these boilerplate autocompletes I can think of: Elm JSON decoders
and encoders, or listing all branches of a <code>case..of</code> expression. Brain-dead
code. A substantial part of this are code patterns that a good LSP server would
give you too, deterministically, without any LLM guessing.</p>

<p>But, somebody needs to write that LSP. And when it doesn’t exist or it isn’t
good enough, it’s pretty nice that LLMs can substitute this for you. And they
autocomplete other scenarios too, for example function bodies, so I think
there’s still some value there, even with a good LSP present.</p>

<p>On the topic of completing function bodies, I think there’s a risk here of just
accepting whatever the LLM suggests, and then reading through it and tweaking
it or accepting it. That’s slightly sketchy to me. I personally find that
reviewing code is much harder than writing it, and it’s hard to switch between
these two contexts on the fly. Maybe this is why (my) reviews of LLM code are
so half-hearted.</p>

<p>So, it seems to me that this way of working with LLMs is safe, as long as you
always have the code to write in your mind and only let the LLM autocomplete if
that code is roughly identical to what you wanted to write. Consider not
pressing <code>Tab</code> if you didn’t have a plan for what to write, or how to implement
something, to stay in the authoring mode instead of switching to a review mode.</p>

<p>Cool, it looks like we have an improvement! I think we’re ready to find another
point in between the usable and the unusable part of the spectrum.</p>

<h2 id="040-localized-cmdk-prompts-in-editor"><code>0.40</code> (localized <code>Cmd+K</code> prompts in editor)</h2>

<div class="theme_fullscreen">
<a href="/assets/images/2026-02-05-the-llm-spectrum-and-responsible-llm-use/spectrum_4.png">
<img src="/assets/images/2026-02-05-the-llm-spectrum-and-responsible-llm-use/spectrum_4.png" alt="The spectrum (0.40)" />
</a>
</div>

<p>There’s another mode of using LLMs, which I like to call the <code>Cmd+K</code> mode
(based on the keyboard shortcut Cursor uses for it).</p>

<p>You’re in your editor (not in the agent chat UI!), select a part of the code,
press <code>Cmd+K</code> and say <em>“please refactor this function to use an exhaustive
switch statement”</em>.</p>

<p>The LLM makes a suggestion, while never overstepping the boundaries of your
selection. Sometimes that means it adds redundant import statements to the top
of the selection—I suspect it doesn’t have the whole file / codebase in its
context so it doesn’t know what’s outside the selection? Would be nice if it
did, it feels inferior in intelligence to the agent chat otherwise—but other
than that it’s pretty sweet.</p>

<p>The localized aspect of it means that you’re the one moving the cursor and
driving this code generation, which I suspect is good for your mental model and
keeping a sane API between the functions and modules. It also means you have
less code to review, and have the context necessary to understand the change or
addition, because you’ve <em>just</em> asked for it. So from the perspective of having
an up-to-date theory of the code in your mind, this is great (although not as
good as writing the code yourself).</p>

<p>There’s a way to perverse this, by selecting the whole file and prompting
<em>“implement X”</em>. That gives you an inferior, lobotomized version of an agent,
while making it harder for yourself to review the code, because now suddenly
the changes are all across the file instead of localized to a function. You
need to build context during review for what the functions inside are doing
now. So I wouldn’t recommend <em>that.</em></p>

<p>I’d say the localized version is right around the limit of what is safe for
work or serious code. (Still unsure whether it’s inside the safe interval or
outside it.)</p>

<p>I value having an up-to-date mental model of the code in my head and being able
to recollect and roughly explain what each high-level part of it does. (I can
do this for some code I haven’t touched in years; I can’t do this for agent-written
code I have willed into existence last week.)</p>

<p>With this localized <code>Cmd+K</code> generation, you haven’t written some parts of the
code anymore, so you might be hazy about how exactly does a step work. The same
way you learn(ed) more at school by writing than by reading, you get a better
picture of the code by writing the code yourself (and struggling to figure out
how to do all the tiny details) than by reviewing the LLM’s output.</p>

<h2 id="others">Others?</h2>

<p>When I set out to write this post, I didn’t quite expect to draw the line at
“agent mode bad”… But here we are!</p>

<p>Maybe there are ways to use agents that I’m not familiar with that somehow
manage to make you hold all the important details about the codebase in your
head, the same way writing the code manually would. I don’t know of them.</p>

<p>You might be screaming at me: you fail using LLMs because you’re not using XYZ!
I know there are ways to use agents with extra structure and product-managerial
practices on top: <a href="https://agentskills.io/home">Agent Skills</a>,
<a href="https://github.com/obra/Superpowers">superpowers</a>,
<a href="https://github.com/github/spec-kit">spec-kit</a>, <a href="https://github.com/snarktank/ralph">Ralph
loops</a> and surely more pop up every day.</p>

<p>I think they live somewhere between the rightmost two points on my made-up
spectrum:</p>

<div class="theme_fullscreen">
<a href="/assets/images/2026-02-05-the-llm-spectrum-and-responsible-llm-use/spectrum_4.png">
<img src="/assets/images/2026-02-05-the-llm-spectrum-and-responsible-llm-use/spectrum_4.png" alt="The full spectrum" />
</a>
</div>

<p>I don’t have enough experience with whether these methodologies make the code
robust enough or not, whether they keep you-the-developer in the loop enough to
keep your mental model of the codebase fleshed out (I really think this is a
big deal), instead of it becoming a black box over time and degrading your
interaction with the agent and the codebase back towards the <code>1.00</code> YOLO vibe
coding extreme.</p>

<p>I’m pretty vanilla when it comes to agents; I’ve tried <code>spec-kit</code> on a compiler project once but it
seems to have hit a wall when given a hard enough sub-task.</p>

<blockquote>
  <p>In my case, the model couldn’t keep conversions between a language AST and
emitted VM bytecode operations straight, emitting sequences that didn’t do
what they should, eg. retrieving an array element at an index.</p>
</blockquote>

<p>It might be interesting to hear from people who <em>finished</em> non-trivial projects
with these structures on top of vanilla LLM agents. I hope they do exist; whenever I read
<a href="https://lobste.rs/">Lobste.rs</a> or <a href="https://news.ycombinator.com/">Hacker
News</a>, there’s a new OSS utility claiming to be
ready for use, but then you spend 2 minutes looking at its output and <a href="https://agents.craft.do/mermaid#sample-6:~:text=%E2%94%8C%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%90%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%E2%94%82%20%20%20%20%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%E2%94%82%20%20Source%20%20%E2%94%9C%E2%94%80-,thickted,-%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%90%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%E2%94%82%20%20%20%20%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%E2%94%82%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%E2%94%94%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%AC%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%98%20%20%20%20%20%20%20%20%E2%94%94%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%BC%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%90%20%20%20%20%20%0A%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%0A%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%0A%20%20%20%20solid%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%0A%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%0A%20%20%20%20%20%20%E2%96%BC%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%E2%96%BC%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%E2%96%BC%20%20%20%20%20%0A%E2%94%8C%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%90%20%20%20%20%20%20%20%20%E2%94%8C%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%90%20%20%20%20%20%E2%94%8C%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%90%0A%E2%94%82%20%20%20%20%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%20%20%20%20%20%E2%94%82%20%20%20%20%20%E2%94%82%20%20%20%20%20%20%20%20%20%20%E2%94%82%0A%E2%94%82%20Target%201">it’s
obviously
wrong</a>
(sorry for the dig).</p>

<p>We seem to have lost the sense of responsibility for quality of the code we
publish the moment we started delegating it to LLMs.</p>

<blockquote>
  <p>Also not mentioned, but possibly worth discussing: using LLMs to learn about
codebases, debug code, find possible optimizations and refactors, etc. Each
would warrant a separate discussion, outside of <em>“whether/how to use agents
to actually write code.”</em></p>
</blockquote>

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

<div class="theme_fullscreen">
<a href="/assets/images/2026-02-05-the-llm-spectrum-and-responsible-llm-use/spectrum_4.png">
<img src="/assets/images/2026-02-05-the-llm-spectrum-and-responsible-llm-use/spectrum_4.png" alt="The full spectrum" />
</a>
</div>

<p>The “agent” part of the spectrum doesn’t seem to overlap with the interval
where the developer ends up knowing what the codebase does, and where the
codebase is healthy.</p>

<p>As of this moment, with my limited experience, I’m really skeptical there’s a
good compromise that both allows using agents and ends up with safe-for-work,
maintainable code and a developer that, over time, maintains intimate
familiarity with the codebase.</p>

<p>So maybe don’t use agents at work?</p>

<p><code>&lt;homer_hides_into_a_bush.gif&gt;</code></p>

<p>There’s also a possibility I’m just uniquely unwilling to review agent code at
a PR scale and bad at forming a mental model by reading alone, and others have a
different experience with agents.</p>

<p>In my frontend developer pre-LLM days, collaborating with other team members on
a shared codebase, the parts I wrote were always <em>much</em> clearer to me than the parts
others wrote that I just reviewed. And I swear I made effort to understand the
changes made by others. It always took having to <em>touch</em> the code to truly
internalize it.</p>

<p>Maybe the secret for responsible LLM use is in intentionally taking the time to
touch the code between agent sessions? Some healthy balance of the two?</p>

<p>Maybe even if one person on the team uses LLMs irresponsibly, but another
writes code manually and cleans things up or vetoes certain patterns in code
reviews, the codebase survives? I don’t know.</p>

<p>But I won’t be all-in on <em>agent use by default</em> anytime soon. If quality of the
code is important and I’m to be responsible for it (note that for some side
projects, prototypes or experiments it’s fine not to!), I will be in my editor,
touching most code myself, only sometimes accepting suggestions from the LLM or
asking it to write small functions. I will (try to) resist delegating most of
the actual coding work to an agent.</p>

<p><code>Tab</code> and <code>Cmd+K</code> or die.</p>

<p><em>P.S.: I can’t wait to read this in a year and cringe at my views.</em></p>

<p><em>P.P.S.: Isn’t it ironic to write this whole thing and have the link to a previous post be about vibe coding a programming language interpreter</em> ⬇️. <em>I’d say that one goes in the</em> “this is a throwaway experiment and quality doesn’t matter” <em>category, so I believe I’m still consistent with myself here!</em></p>]]></content><author><name>Martin Janiczek</name></author><category term="Posts" /><summary type="html"><![CDATA[In my mind there’s a spectrum going from 0.00 - “all manual” (where we all were a few years ago) to 1.00 - “vibe coding” (you’re a product manager and don’t look at any code at all, just spamming “XYZ doesn’t work for me, fix it”).]]></summary></entry><entry><title type="html">FAWK: LLMs can write a language interpreter</title><link href="https://martin.janiczek.cz/2025/11/21/fawk-llms-can-write-a-language-interpreter.html" rel="alternate" type="text/html" title="FAWK: LLMs can write a language interpreter" /><published>2025-11-21T00:00:00+00:00</published><updated>2025-11-21T00:00:00+00:00</updated><id>https://martin.janiczek.cz/2025/11/21/fawk-llms-can-write-a-language-interpreter</id><content type="html" xml:base="https://martin.janiczek.cz/2025/11/21/fawk-llms-can-write-a-language-interpreter.html"><![CDATA[<p>After reading the book <a href="https://www.awk.dev/">The AWK Programming Language</a>
<em>(recommended!)</em>, I was planning to try <a href="https://en.wikipedia.org/wiki/AWK">AWK</a>
out on this year’s Advent of Code. Having some time off from work this week, I
tried to implement <a href="https://adventofcode.com/2016/day/22">one of the problems</a>
in it to get some practice, set up my tooling, see how hard AWK would be,
and… I found I’m FP-pilled.</p>

<p>I <em>knew</em> I’m addicted to the combination of algebraic data types (tagged unions)
and exhaustive pattern matching, but what got me this time was immutability,
lexical scope and the basic human right of being allowed to return arrays from
functions.</p>

<p>Part 1 of the Advent of Code problem was easy enough, but for part 2 (basically
a shortest path search with a twist, to not spoil too much), I found myself
unable to switch from my usual <a href="/2023/06/27/fp-pattern-list-of-todos.html">functional BFS
approach</a>
to something mutable, and ended up trying to implement my functional approach in
AWK.</p>

<p>It got hairy very fast: I needed to implement:</p>
<ul>
  <li>hashing of strings and 2D arrays (by piping to <code>md5sum</code>)</li>
  <li>a global <del>set</del> array of seen states</li>
  <li>a way to serialize and deserialize a 2D array to/from a string</li>
  <li>and a few associative arrays for retrieving this serialized array by its
hash.</li>
</ul>

<p>I was very lost by the time I had all this; I spent hours just solving what felt
like <em>accidental complexity</em>; things that I’d take for granted in more modern
languages.</p>

<p>Now, I know nobody said AWK is modern, or functional, or that it promises any
convenience for anything other than one-liners and basic scripts that fit under
a handful of lines. I don’t want to sound like I expect AWK to do any of this;
I knew I was stretching the tool when going in. But I couldn’t shake the feeling
that there’s a beautiful AWK-like language within reach, an iteration on the AWK
design (the pattern-action way of thinking is beautiful) that also gives us a
few of the things programming language designers have learnt over the 48 years
since AWK was born.</p>

<h2 id="dreaming-of-functional-awk">Dreaming of functional AWK</h2>

<p>Stopping my attempts to solve the AoC puzzle in pure AWK, I wondered: what am I
missing here?</p>

<p>What if AWK had <strong>first-class arrays?</strong></p>

<pre><code class="language-awk">BEGIN {
  # array literals
  normal   = [1, 2, 3]
  nested   = [[1,2], [3,4]]
  assoc    = ["foo" =&gt; "bar", "baz" =&gt; "quux"]
  multidim = [(1,"abc") =&gt; 999]

  five = range(1,5)
  analyze(five)
  print five  # --&gt; still [1, 2, 3, 4, 5]! was passed by value
}

function range(a,b) {
  r = []
  for (i = a; i &lt;= b; i++) {
    r[length(r)] = i
  }
  return r  # arrays can be returned!
}

function analyze(arr) {
  arr[0] = 100
  print arr[0]  # --&gt; 100, only within this function
}
</code></pre>

<p>What if AWK had <strong>first-class functions and lambdas?</strong></p>

<pre><code class="language-awk">BEGIN {
  # construct anonymous functions
  double = (x) =&gt; { x * 2 }
  add = (a, b) =&gt; { c = a + b; return c }

  # functions can be passed as values
  apply = (func, value) =&gt; { func(value) }

  print apply(double,add(1,3))  # --&gt; 8
  print apply(inc,5)  # --&gt; 6
}

function inc(a) { return a + 1 }
</code></pre>

<p>What if AWK had <strong>lexical scope</strong> instead of dynamic scope?</p>

<pre><code class="language-awk"># No need for this hack anymore ↓     ↓
#function foo(a, b         ,local1, local2) {
function foo(a, b) {
  local1 = a + b
  local2 = a - b
  return local1 + local2
}

BEGIN {
  c = foo(1,2)
  print(local1)  # --&gt; 0, the local1 from foo() didn't leak!
}
</code></pre>

<p>What if AWK had <strong>explicit globals</strong>, and everything else was <strong>local by default?</strong></p>

<pre><code class="language-awk">BEGIN { global count }
END {
  foo()
  print count  # --&gt; 1
  print mylocal # --&gt; 0, didn't leak
}
function foo() { count++; mylocal++ }
</code></pre>

<p>(This one, admittedly, might make programs a bit more verbose. I’m willing to
pay that cost.)</p>

<p>What if AWK had <strong>pipelines?</strong> (OK, now I’m reaching for syntax sugar…)</p>

<pre><code class="language-awk">BEGIN {
  result = [1, 2, 3, 4, 5] 
      |&gt; filter((x) =&gt; { x % 2 == 0 })
      |&gt; map((x) =&gt; { x * x })
      |&gt; reduce((acc, x) =&gt; { acc + x }, 0)

  print "Result:", result
}
</code></pre>

<h2 id="making-it-happen">Making it happen</h2>

<blockquote>
  <p>TL;DR: <a href="https://github.com/Janiczek/fawk"><code>Janiczek/fawk</code> on GitHub</a></p>
</blockquote>

<p>Now for the crazy, LLM-related part of the post. I didn’t want to spend days
implementing AWK from scratch or tweaking somebody else’s implementation. So I
tried to use Cursor Agent for a larger task than I usually do (I tend to ask
for very small targeted edits), and asked Sonnet 4.5 for <a href="https://github.com/Janiczek/fawk/pull/1/files">a README with code
examples</a>, and then <a href="https://github.com/Janiczek/fawk/pull/2/files">a full
implementation in Python</a>.</p>

<p>And it did it.</p>

<blockquote>
  <p>Note: I also asked for implementations in C, Haskell and Rust at the same
time, not knowing if any of the four would succeed, and they all seem to have
produced code that at least compiles/runs. I haven’t tried to test them or
even run them though. The PRs are
<a href="https://github.com/Janiczek/fawk/pulls?q=is%3Apr+is%3Aclosed">here</a>.</p>
</blockquote>

<p>I was very impressed—I still am! I expected the LLM to stumble and flail
around and ultimately get nothing done, but it did what I asked it for (gave me
an interpreter that could run <em>those specific examples</em>), and over the course
of a few chat sessions, I guided it towards implementing more and more of “the
rest of AWK”, together with an excessive amount of end-to-end tests.</p>

<p><a href="https://github.com/Janiczek/fawk/tree/main/tests">Take a look at those tests!</a></p>

<p>The only time I could see it struggle was when I asked it to implement arbitrary
precision floating point operations without using an external library like
<code>mpmath</code>. It attempted to use Taylor series, but couldn’t get it right for at
least a few minutes. I chickened out and told it to <code>uv add mpmath</code> and simplify
the interpreter code. In a moment it was done.</p>

<p>Other things that I thought it would choke on, like <code>print</code> being both a
statement (with <code>&gt;</code> and <code>&gt;&gt;</code> redirection support) and an expression, or
multi-dimensional arrays, or multi-line records, these were all implemented
correctly. Updating the test suite to also check for backwards compatibility
with <a href="https://www.gnu.org/software/gawk/">GAWK</a> - not an issue. Lexical scoping
and tricky closure environment behaviour - handled that just fine.</p>

<h2 id="what-now">What now?</h2>

<p>As the cool kids say, I have to <em>update my priors.</em> The frontier of what the
LLMs can do has moved since the last time I tried to vibe-code something. I
didn’t expect to have a working interpreter <em>the same day</em> I dreamt of a new
programming language. It now seems possible.</p>

<p>The downside of vibe coding the whole interpreter is that I have zero knowledge
of the code. I only interacted with the agent by telling it to implement a
thing and write tests for it, and I only <em>really</em> reviewed the tests. I reckon
this would be an issue in the future when I want to manually make some change
in the actual code, because I have no familiarity with it.</p>

<blockquote>
  <p>This also opened new questions for me wrt. my other projects where I’ve
previously run out of steam, eg. trying to implement a <a href="https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system">Hindley-Milner type
system</a> for my
dream forever-WIP programming language <a href="https://cara-lang.com/">Cara</a>. It seems
I can now just ask the LLM to do it, and it will? But then, I don’t want to fall
into the trap where I am no longer able to work on the codebase myself. I want
to be familiar with and able to tinker on the code. I’d need to spend my time
reviewing and reading code instead of writing everything myself. Perhaps that’s
OK.</p>
</blockquote>

<p>Performance of FAWK might be an issue as well, though right now it’s a non-goal,
given my intended use case is throwaway scripts for Advent of Code, nothing
user-facing.  And who knows, based on what I’ve seen, maybe I can instruct it to
<em>rewrite it in Rust</em> and have a decent chance of success?</p>

<p>For now, I’ll go dogfood my shiny new vibe-coded black box of a programming
language on the Advent of Code problem (and as many of the 2025 puzzles as I
can), and see what rough edges I can find. I expect them to be equal parts “not
implemented yet” and “unexpected interactions of new PL features with the old
ones”.</p>

<p>If you’re willing to jump through some Python project dependency hoops, you can
try to use FAWK too at your own risk, at <a href="https://github.com/Janiczek/fawk"><code>Janiczek/fawk</code> on
GitHub</a>.</p>]]></content><author><name>Martin Janiczek</name></author><category term="Posts" /><summary type="html"><![CDATA[After reading the book The AWK Programming Language (recommended!), I was planning to try AWK out on this year’s Advent of Code. Having some time off from work this week, I tried to implement one of the problems in it to get some practice, set up my tooling, see how hard AWK would be, and… I found I’m FP-pilled.]]></summary></entry><entry><title type="html">Writing your own BEAM</title><link href="https://martin.janiczek.cz/2025/11/09/writing-your-own-beam.html" rel="alternate" type="text/html" title="Writing your own BEAM" /><published>2025-11-09T00:00:00+00:00</published><updated>2025-11-09T00:00:00+00:00</updated><id>https://martin.janiczek.cz/2025/11/09/writing-your-own-beam</id><content type="html" xml:base="https://martin.janiczek.cz/2025/11/09/writing-your-own-beam.html"><![CDATA[<p>This is my <a href="https://codebeameurope.com/">Code BEAM Europe 2025</a> talk, converted to a blogpost.</p>

<blockquote>
  <p>EDIT 2025-11-10: Hacker News folks pointed out it might not be clear to everybody what BEAM is: it’s the virtual machine for languages like Erlang, Elixir and Gleam. <a href="https://en.wikipedia.org/wiki/BEAM_(Erlang_virtual_machine)">See Wikipedia.</a></p>
</blockquote>

<p>I was always fascinated with BEAM, how it allowed easy spawning of processes that didn’t share state, allowed for sending and selectively receiving messages, and linking to each other thus enabling creation of supervision trees.</p>

<p>It’s an interesting set of primitives that interact in a nice way, and are in my view responsible for much of the appeal of BEAM languages. I wanted to see how much it takes to support these primitives, and I set out to write my own toy MVP implementation of BEAM.</p>

<p>As a disclaimer, I haven’t read <a href="https://blog.stenmans.org/theBeamBook/">The BEAM Book</a> yet, and how I do things might differ substantially from how the real BEAM does things. This is an exploration from first principles based on how I perceive BEAM from the outside, and doesn’t aim for truthfulness to the reference implementation, real world usefulness nor performance.</p>

<p>The below examples are written in Elm, but if you can express it in Elm, you can express it in anything (it’s purely functional so there’s no mutation, it’s single threaded and has no concurrency primitives, etc.).</p>

<h2 id="ast-representation">AST representation</h2>

<p>I will only be making the scheduler and its main loop, not a full-blown language or VM. This allows me to only <strong>keep a few hardcoded examples around</strong> and skip writing a parser, CLI and a bunch more parts that a real compiler would have.</p>

<p>In the interest of skipping as much work as possible, I’ll be using <strong>continuation passing style (CPS)</strong> for the example programs instead of the usual “list of statements” style:</p>

<pre><code class="language-elm">-- ☑️ YES: continuations
type Program
    = End
    | Work Int K
    | Spawn Program KPid
    | Send Pid String K
    | Receive String K
    | Crash
    | Link Pid K

type alias K =
    () -&gt; Program

type alias KPid =
    Pid -&gt; Program

-- ❌ NO: list of statements
type Stmt
    = Let String Expr
    | Work Int
    | Spawn Program
    | Send Pid String
    | Receive String Program
    | Crash
    | Link Pid

type alias Program =
    List Stmt
</code></pre>

<p>This means I don’t have to care about environments, bindings, scopes, return values, expressions and so on, as this will be handled by the continuation arguments in the host language:</p>

<pre><code class="language-elm">ex5 : Program
ex5 =
    Spawn ex5Child       &lt;| \childPid -&gt;
    Send childPid "Ping" &lt;| \() -&gt;
    End

ex5Child =
    Work 10 &lt;| \() -&gt;
    End
</code></pre>

<p>In case you’re having issues reading the <code>&lt;|</code> operator, you can imagine a pair of parentheses instead:</p>

<pre><code class="language-elm">ex5 : Program
ex5 =
    Spawn ex5Child       (\childPid -&gt;
    Send childPid "Ping" (\() -&gt;
    End
    ))

ex5Child =
    Work 10 (\() -&gt;
    End
    )
</code></pre>

<h2 id="instruction-end">Instruction: <code>End</code></h2>

<p>The continuations in all the non-terminal instructions force us to provide at least one terminal, otherwise we couldn’t write a valid <code>Program</code> value.</p>

<p>Let’s then start by implement one of the terminals, <code>End</code>. It’s a no-op, but it will allow me to show off the structure of the scheduler.</p>

<pre><code class="language-elm">type Program =
    End

ex1 : Program
ex1 =
    End

type alias Scheduler =
    { program : Program }

init : Program -&gt; Scheduler
init program =
    { program = program }

step : Scheduler -&gt; Scheduler
step sch =
    case sch.program of
        End -&gt; sch
</code></pre>

<p><a href="https://ellie-app.com/x4ykjfHJ5Sra1">Try it online,</a> or try the visualizer below:</p>

<script>
let app = null;
</script>

<script src="/assets/js/WritingYourOwnBeamDemo1.elm.js"></script>

<div class="theme_fullscreen">
    <div id="demo1" style="color: red">Oh no, the visualizer didn't load!</div>
</div>
<script>
app = Elm.WritingYourOwnBeam.Demo1.init({
    node: document.getElementById('demo1'),
});
app.ports.jumpToBottomOfTraces.subscribe((traceId) => {
    document.getElementById(traceId).scrollTop = document.getElementById(traceId).scrollHeight;
});
</script>

<p>Everything will revolve around this <code>Scheduler</code> type and its <code>step</code> function. Now let’s expand our capabilities.</p>

<h2 id="instruction-work">Instruction: <code>Work</code></h2>

<p>Instead of wasting time implementing instructions for <em>actual</em> work (mathematic operators, function calls, etc.), let’s encompass this all with a dummy <code>Work</code> instruction, holding the amount of work (in units that will start making sense soon) and a continuation with what to do after the work:</p>

<pre><code class="language-elm">type Program
    = End
    -- Added:
    | Work Int K

type alias K =
    () -&gt; Program

ex2 : Program
ex2 =
    Work 5 &lt;| \() -&gt;
    End
</code></pre>

<p>The example holds a program that will “work” for 5 units of work then end.</p>

<p>We need to add this new instruction to our <code>step</code> function:</p>

<pre><code class="language-elm">step : Scheduler -&gt; Scheduler
step sch =
    case sch.program of
        End -&gt; sch
        -- Added:
        Work n k -&gt; { sch | program = k () }
</code></pre>

<p>For now we’ll just ignore how much work it’s supposed to be, and continue with the rest of the program (result of calling the continuation: <code>k ()</code>).</p>

<p><a href="https://ellie-app.com/x4LP34t3QhPa1">Try it online,</a> or try the visualizer below:</p>

<script src="/assets/js/WritingYourOwnBeamDemo2.elm.js"></script>

<div class="theme_fullscreen">
    <div id="demo2" style="color: red">Oh no, the visualizer didn't load!</div>
</div>
<script>
app = Elm.WritingYourOwnBeam.Demo2.init({
    node: document.getElementById('demo2'),
});
app.ports.jumpToBottomOfTraces.subscribe((traceId) => {
    document.getElementById(traceId).scrollTop = document.getElementById(traceId).scrollHeight;
});
</script>

<h2 id="instruction-spawn">Instruction: <code>Spawn</code></h2>

<p>Let’s do something <em>interesting</em>! We’ll add a way to spawn other processes, thus making our programs concurrent.</p>

<pre><code class="language-elm">type Program =
    -- ...
    | Spawn Program KPid

type alias KPid =
    Pid -&gt; Program

type alias Pid =
    Int

ex3 : Program
ex3 =
    Work 5         &lt;| \() -&gt;
    Spawn ex3Child &lt;| \childPid -&gt;
    Work 5         &lt;| \() -&gt;
    End

ex3Child : Program
ex3Child =
    Work 10 &lt;| \() -&gt;
    Work 10 &lt;| \() -&gt;
    End
</code></pre>

<p>Whenever we spawn another process, we’ll receive its PID in the continuation, which will be useful later for messaging and other tasks.</p>

<p>This marks a big change in our <code>Scheduler</code>: suddenly we have to track multiple processes instead of just one!</p>

<pre><code class="language-elm">type alias Scheduler =
    { processes : Dict Pid Proc
    , nextUnusedPid : Pid
    , readyQueue : Queue Pid
    }

type alias Proc =
    { program : Program }

init : Program -&gt; Scheduler
init program =
    { processes = Dict.empty
    , nextUnusedPid = 0
    , readyQueue = Queue.empty
    }
        |&gt; spawn program
        |&gt; Tuple.first -- discard the spawned PID

spawn : Program -&gt; Scheduler -&gt; ( Scheduler, Pid )
spawn program sch =
    let pid = sch.nextUnusedPid in
    ( { sch
        | processes =
            sch.processes
                |&gt; Dict.insert pid (initProc program)
        , nextUnusedPid = pid + 1
      }
        |&gt; enqueue pid
    , pid
    )

initProc : Program -&gt; Proc
initProc program =
    { program = program }

enqueue : Pid -&gt; Scheduler -&gt; Scheduler
enqueue pid sch =
    { sch
        | readyQueue =
            if List.member pid (Queue.toList sch.readyQueue)
            then sch.readyQueue
            else sch.readyQueue |&gt; Queue.enqueue pid
    }
</code></pre>

<p>We hold the processes in a <code>Dict</code> collection now, there’s a bit of bookkeeping for incrementing PIDs, and a new concept: the “ready queue.”</p>

<p>This queue will tell our scheduler which process to run next. This means our <code>step</code> function needs to change considerably: previously it was able to just pick the (only) program with <code>sch.program</code>, but now it needs to pick a PID from the queue, then find it in the dictionary, <em>then</em> run it:</p>

<pre><code class="language-elm">step : Scheduler -&gt; Scheduler
step sch =
    case Queue.dequeue sch.readyQueue of
        Nothing -&gt; sch
        Just ( pid, restOfQueue ) -&gt;
            let newSch = { sch | readyQueue = restOfQueue } in
            case Dict.get pid newSch.processes of
                Nothing   -&gt; newSch
                Just proc -&gt; newSch |&gt; stepInner pid proc

stepInner : Pid -&gt; Proc -&gt; Scheduler -&gt; Scheduler
stepInner pid proc sch =
    case proc.program of
        End -&gt; sch

        Work n k -&gt;
            sch
                |&gt; updateProc pid (setProgram (k ()))
                |&gt; enqueue pid

updateProc : Pid -&gt; (Proc -&gt; Proc) -&gt; Scheduler -&gt; Scheduler
updateProc pid fn sch =
    { sch | processes =
        sch.processes
            |&gt; Dict.update pid (Maybe.map fn)
    }

setProgram : Program -&gt; Proc -&gt; Proc
setProgram newProgram proc =
    { proc | program = newProgram }
</code></pre>

<p>The specifics of <code>stepInner</code> had to change as well: we can’t set the single <code>sch.program</code> anymore, we need to update an entry for a PID in the processes dictionary.</p>

<p>Let’s not forget about the new instruction:</p>

<pre><code class="language-elm">stepInner pid proc sch =
    -- ...
    Spawn childProgram kpid -&gt;
        let ( schWithChild, childPid ) =
                sch |&gt; spawn childProgram
        in schWithChild
               |&gt; updateProc pid (setProgram (kpid childPid))
               |&gt; enqueue pid
</code></pre>

<p>We reuse the <code>spawn</code> function from before. It gives us the child’s PID, which we can use to access the rest of the parent program via the continuation: <code>kpid childPid</code>.</p>

<p>We need to remember to enqueue the parent again (the program given by the continuation hasn’t run yet); the child has already been enqueued in the <code>spawn</code> function.</p>

<p><a href="https://ellie-app.com/x4LQBXDbWb3a1">Try it online,</a> or try the visualizer below.</p>

<p>Our scheduler now takes 7 steps to finish the whole program, which corresponds to the 7 instructions in our initial program.</p>

<script src="/assets/js/WritingYourOwnBeamDemo3.elm.js"></script>

<div class="theme_fullscreen">
    <div id="demo3" style="color: red">Oh no, the visualizer didn't load!</div>
</div>
<script>
app = Elm.WritingYourOwnBeam.Demo3.init({
    node: document.getElementById('demo3'),
});
app.ports.jumpToBottomOfTraces.subscribe((traceId) => {
    document.getElementById(traceId).scrollTop = document.getElementById(traceId).scrollHeight;
});
</script>

<h2 id="reduction-budget">Reduction budget</h2>

<p>Can you see any potential issues with our current Scheduler?</p>

<p>The concurrency we have implemented is <strong>cooperative</strong>: a started process won’t be stopped by the scheduler in the middle. Consider this program:</p>

<pre><code class="language-elm">ex4 : Program
ex4 =
    Work 5         &lt;| \() -&gt;
    Spawn ex4Child &lt;| \childPid -&gt;
    Work 5         &lt;| \() -&gt;
    End

ex4Child : Program
ex4Child =
    Work 999 &lt;| \() -&gt; -- !!!
    Work 10  &lt;| \() -&gt;
    End
</code></pre>

<p>This only differs from example 3 by the amount of work the child is doing. The parent can’t finish its tiny bit of work after the spawn until the child finishes its 999 units of work.</p>

<p>The way BEAM solves this is with a <strong>reduction budget:</strong> it creates an illusion of <strong>preemptive</strong> scheduling on top of the cooperative one by inserting yield points after every function call, decrementing its reduction budget in each, and once the budget reaches 0, the scheduler will pause the process and start another one from the queue.</p>

<p>This works surprisingly well: in BEAM languages, you iterate through lists via recursion →  there’s a lot of function calls →  a lot of yield points.</p>

<p>We’ll do something similar in our toy implementation: introduce a reduction budget, and make the <code>Work</code> instruction only do as much “work” as the budget allows.</p>

<pre><code class="language-elm">reductionBudget : Int
reductionBudget =
    7 -- BEAM sets this to 4000.

step : Scheduler -&gt; Scheduler
step sch =
    -- ...
    sch |&gt; stepInner pid proc {- added: -} reductionBudget

stepInner : Pid -&gt; Proc -&gt; Int -&gt; Scheduler -&gt; Scheduler
stepInner pid proc budget sch =
    if budget &lt;= 0
    then sch
         |&gt; setProc pid proc
         |&gt; (if shouldEnqueue proc
             then enqueue pid
             else identity)
    else -- ...

shouldEnqueue : Proc -&gt; Bool
shouldEnqueue proc =
    case proc.program of
        -- Optimization: if we ended up on `End`,
        -- we don't need to run again.
        End -&gt; False
        Work _ _ -&gt; True
        Spawn _ _ -&gt; True

setProc : Pid -&gt; Proc -&gt; Scheduler -&gt; Scheduler
setProc pid newProc sch =
    sch
        |&gt; updateProc pid (\_ -&gt; newProc)
</code></pre>

<p>Above we’re dealing with the case where the process ran out of the budget. The scheduler will remember where it ended, re-enqueue it if there’s more work to do (if we’re not at the <code>End</code> instruction), and stop the current step.</p>

<p>Let’s flesh out the rest of <code>stepInner</code>:</p>

<pre><code class="language-elm">stepInner pid proc budget sch =
    -- ...
    else
    let
        stop : Scheduler -&gt; Scheduler
        stop sch_ =
            sch_ |&gt; stepInner pid program 0

        continue : Program -&gt; Int -&gt; Scheduler -&gt; Scheduler
        continue newProgram newBudget sch_ =
            sch_ |&gt; stepInner pid newProgram newBudget
    in
    -- ...
</code></pre>

<p>Here I’m making helpers for working with the budget. <code>stop</code> sets the budget to 0 and recurses, so that we go straight to the <code>if budget &lt;= 0 then ...</code> code path.</p>

<p><code>continue</code> instead sets the budget to some arbitrary number we provided. Usually we’ll decrement the current budget by 1, but in case of <code>Work</code> we’ll jump in larger increments.</p>

<p>Let’s use them:</p>

<pre><code class="language-elm">stepInner pid proc budget sch =
    -- ...
    in
    case program of
        End -&gt; sch |&gt; stop

        Spawn childProgram kpid -&gt;
            let ( schWithChild, childPid ) =
                    sch |&gt; spawn childProgram
            in schWithChild
                   |&gt; continue (kpid childPid) (budget - 1)

        Work n k -&gt;
            if n &lt;= 0
            then sch |&gt; enqueue pid
                     |&gt; continue (k ()) budget
            else let workDone = min n budget
                     workRemaining = n - workDone
                     budgetRemaining = budget - workDone
                 in sch |&gt; continue (Work workRemaining k)
                                    budgetRemaining
</code></pre>

<p>The <code>Work</code> instruction now works completely differently: instead of doing all the work at once (going straight for <code>k ()</code>), it now finally cares about the amount of work present.</p>

<p>We will only continue with <code>k ()</code> if there’s no more work to be done (<code>n &lt;= 0</code>).</p>

<p>Otherwise we calculate how much work <em>can</em> be done, and update the remaining work and budget accordingly.</p>

<table>
  <thead>
    <tr>
      <th>Budget</th>
      <th>Work</th>
      <th>Work done</th>
      <th>Work remaining</th>
      <th>Budget remaining</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>7</td>
      <td>5</td>
      <td>5</td>
      <td>0</td>
      <td>2</td>
    </tr>
    <tr>
      <td>7</td>
      <td>7</td>
      <td>7</td>
      <td>0</td>
      <td>0</td>
    </tr>
    <tr>
      <td>7</td>
      <td>9</td>
      <td>7</td>
      <td>2</td>
      <td>0</td>
    </tr>
  </tbody>
</table>

<p><a href="https://ellie-app.com/x4MFY5njY4Xa1">Try it online,</a> or try the visualizer below.</p>

<script src="/assets/js/WritingYourOwnBeamDemo4.elm.js"></script>

<div class="theme_fullscreen">
    <div id="demo4" style="color: red">Oh no, the visualizer didn't load!</div>
</div>
<script>
app = Elm.WritingYourOwnBeam.Demo4.init({
    node: document.getElementById('demo4'),
});
app.ports.jumpToBottomOfTraces.subscribe((traceId) => {
    document.getElementById(traceId).scrollTop = document.getElementById(traceId).scrollHeight;
});
</script>

<p>Take a look at the first few steps after the child spawns:</p>

<table>
  <thead>
    <tr>
      <th>PID 0 (parent)</th>
      <th>PID 1 (child)</th>
      <th>Ready queue</th>
      <th>Action</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Work 4</td>
      <td>Work 999</td>
      <td>1,0</td>
      <td>PID 1 runs, 999 -&gt; 992</td>
    </tr>
    <tr>
      <td>Work 4</td>
      <td>Work 992</td>
      <td>0,1</td>
      <td>PID 0 runs, 4 -&gt; 0 -&gt; End</td>
    </tr>
    <tr>
      <td>End</td>
      <td>Work 992</td>
      <td>1</td>
      <td>PID 1 runs, 992 -&gt; 985</td>
    </tr>
    <tr>
      <td>End</td>
      <td>Work 985</td>
      <td>1</td>
      <td>…</td>
    </tr>
  </tbody>
</table>

<p>Thus, even though the child has a lot of work to be done, the scheduler preempts and only lets it do the work in chunks of 7, and the parent process gets a chance to do some of its work as well.</p>

<h2 id="instruction-send">Instruction: <code>Send</code></h2>

<p>Spawning processes without letting them communicate is not very useful. It might help move computations off the main thread, but obviously we’ll want some <a href="https://en.wikipedia.org/wiki/Inter-process_communication">inter-process communication</a> eventually.</p>

<p>Let’s add a way to send messages to processes. (Receiving them will come later.)</p>

<pre><code class="language-elm">type Program
    = -- ...
    | Send Pid String K

shouldEnqueue : Proc -&gt; Bool
shouldEnqueue proc =
    -- ...
    Send _ _ _ -&gt; True

ex5 : Program
ex5 =
    Spawn ex5Child       &lt;| \childPid -&gt;
    Send childPid "Ping" &lt;| \() -&gt;
    End

ex5Child : Program
ex5Child =
    Work 10 &lt;| \() -&gt;
    End
</code></pre>

<p>To implement this <code>Send</code> instruction, we’ll need to introduce the concept of <strong>mailboxes:</strong></p>

<pre><code class="language-elm">type alias Proc =
    { program : Program
    -- Added:
    , mailbox : Queue String
    }
</code></pre>

<p>Sending a message will be done by putting the message into this mailbox. We can do that because we have access to the whole scheduler, we are not limited to just the current process’ resources:</p>

<pre><code class="language-elm">stepInner pid proc budget sch =
    -- ...
    Send destinationPid message k -&gt;
        sch
            |&gt; send destinationPid message
            |&gt; continue (k ()) (budget - 1)

send : Pid -&gt; String -&gt; Scheduler -&gt; Scheduler
send destinationPid message sch =
    sch
        |&gt; updateProc destinationPid (enqueueMessage message)
        |&gt; enqueue destinationPid

enqueueMessage : String -&gt; Proc -&gt; Proc
enqueueMessage message proc =
    { proc | mailbox = proc.mailbox |&gt; Queue.enqueue message }
</code></pre>

<p>When we send a message to a process, we also enqueue it to make sure it has a chance to process it. This will become important later, when processes go to sleep (ie. don’t enqueue) after not finding any interesting message for their selective receive. We’ll get there!</p>

<p><a href="https://ellie-app.com/x4Nmv37xMgwa1">Try it online,</a> or try the visualizer below.</p>

<script src="/assets/js/WritingYourOwnBeamDemo5.elm.js"></script>

<div class="theme_fullscreen">
    <div id="demo5" style="color: red">Oh no, the visualizer didn't load!</div>
</div>
<script>
app = Elm.WritingYourOwnBeam.Demo5.init({
    node: document.getElementById('demo5'),
});
app.ports.jumpToBottomOfTraces.subscribe((traceId) => {
    document.getElementById(traceId).scrollTop = document.getElementById(traceId).scrollHeight;
});
</script>

<p>The child has the message in its mailbox, but can’t react to it. Let’s fix that!</p>

<h2 id="instruction-receive">Instruction: <code>Receive</code></h2>

<pre><code class="language-elm">type Program
    = -- ...
    | Receive String K
</code></pre>

<p>This is a substantial simplification from what the real BEAM needs to support: Erlang receive statement allows for multiple branches, pattern matching inside the branches, timeouts when there’s no interesting message present for a certain amount of time, and so on.</p>

<p>We will instead only support a single string message with no destructuring. The process won’t continue until this specific string is found in the mailbox.</p>

<pre><code class="language-elm">ex6 : Program
ex6 =
    Spawn ex6Child       &lt;| \childPid -&gt;
    Send childPid "Ping" &lt;| \() -&gt;
    End

ex6Child : Program
ex6Child =
    Receive "Ping" &lt;| \() -&gt; 
    Work 10        &lt;| \() -&gt;
    End
</code></pre>

<p>We can make an interesting optimization in the <code>shouldEnqueue</code> function:</p>

<pre><code class="language-elm">shouldEnqueue proc =
    -- ...
    -- Optimization: we don't need to `Receive`
    -- if there's no interesting message.
    Receive wantedMsg _ -&gt;
        Queue.toList proc.mailbox
            |&gt; List.any (\msg -&gt; msg == wantedMsg)
</code></pre>

<p>This means we won’t reenqueue a process at the end of <code>stepInner</code> if it’s waiting for a message that’s not present in its mailbox. There’s no reason for the process to try again until a new message is received, so the process will instead go to sleep and wait to be woken up later in the <code>send</code> function.</p>

<p>There’s a wall of code coming up, brace yourselves! When interpreting the <code>Receive</code> instruction, we’ll go through messages until we find the wanted one. If we find it, remove it from the mailbox and use the continuation, otherwise go to sleep with the mailbox intact.</p>

<pre><code class="language-elm">stepInner pid proc budget sch =
    -- ...
    continue_ : Proc -&gt; Int -&gt; Scheduler -&gt; Scheduler
    continue_ newProc newBudget sch_ =
        sch_ |&gt; stepInner pid newProc newBudget
    -- ...
    Receive wantedMsg k -&gt;
        let processMessages : List String -&gt; Queue String -&gt; Scheduler
            processMessages unmatchedStartRev restOfMailbox =
                case Queue.dequeue restOfMailbox of
                    -- NO MORE MSGS TO CHECK
                    Nothing -&gt;
                        sch |&gt; stop

                    Just ( msg, restOfMailboxWithoutThis ) -&gt;
                        if msg == wantedMsg then
                            -- FOUND IT
                            let newMailbox =
                                  Queue.fromList
                                      (List.reverse unmatchedStartRev
                                          ++ Queue.toList restOfMailboxWithoutThis)
                            in
                                sch |&gt; continue_
                                           (proc
                                               |&gt; setMailbox newMailbox
                                               |&gt; setProgram (k ())
                                           )
                                           (budget - 1)

                        else 
                            -- TRY NEXT
                            processMessages
                                 (msg :: unmatchedStartRev)
                                 restOfMailboxWithoutThis
        in
        processMessages [] proc.mailbox

setMailbox : Queue String -&gt; Proc -&gt; Proc
setMailbox newMailbox proc =
    { proc | mailbox = newMailbox }
</code></pre>

<p>This code is not very elegant due to plucking a message from the middle of a queue, but it does what I described in the previous paragraph.</p>

<p><a href="https://ellie-app.com/x4Th2jNCtFWa1">Try it online,</a> or try the visualizer below.</p>

<script src="/assets/js/WritingYourOwnBeamDemo6.elm.js"></script>

<div class="theme_fullscreen">
    <div id="demo6" style="color: red">Oh no, the visualizer didn't load!</div>
</div>
<script>
app = Elm.WritingYourOwnBeam.Demo6.init({
    node: document.getElementById('demo6'),
});
app.ports.jumpToBottomOfTraces.subscribe((traceId) => {
    document.getElementById(traceId).scrollTop = document.getElementById(traceId).scrollHeight;
});
</script>

<p>You can see things have lined up nicely: process 1 has <code>"Ping"</code> in its mailbox and also is about to try and <code>Receive "Ping"</code>. In the next step the message is gone and the process is doing <code>Work</code>. Success!</p>

<h2 id="instruction-crash-link">Instruction: <code>Crash</code>, <code>Link</code></h2>

<p>For the last piece of the puzzle, let’s look at the feature that gives rise to supervision trees: <strong>linking</strong> processes together.</p>

<p>Linking is bidirectional; the scheduler will send a system message to the other side of the link whenever a linked process exits (in our example, crashes). The receiving side can choose to react to this exit signal: respawn the other process? Crash ourselves? Log it somewhere and do cleanup?</p>

<blockquote>
  <p>Note: BEAM also has <strong>monitors.</strong> These are one-directional, and I’ll skip them in this blogpost.</p>
</blockquote>

<pre><code class="language-elm">type Program
    = -- ...
    | Crash
    | Link Pid K

type alias Proc =
    { -- ...
    , links : Set Pid
    }

initProc program =
    { -- ...
    , links = Set.empty
    }

ex7 : Program
ex7 =
    Spawn ex7Child &lt;| \childPid -&gt;
    Link childPid  &lt;| \() -&gt;
    Receive ("CRASH: " ++ String.fromInt childPid) &lt;| \() -&gt;
    End

ex7Child : Program
ex7Child =
    Crash

shouldEnqueue proc =
    -- ...
    Crash    -&gt; True
    Link _ _ -&gt; True
</code></pre>

<p>Why <code>Crash -&gt; True</code>? The <code>Crash</code> instruction is a terminal, but it has work to do inside (sending the system messages), so we’ll enqueue it if it hasn’t run yet. (We’ll replace <code>Crash</code> with <code>End</code> after doing that work.)</p>

<pre><code class="language-elm">stepInner pid proc budget sch =
    -- ...
    Link linkedPid k -&gt;
        sch
            |&gt; link pid linkedPid
            |&gt; continue (k ()) (budget - 1)

link : Pid -&gt; Pid -&gt; Scheduler -&gt; Scheduler
link pid1 pid2 sch =
    sch
        |&gt; updateProc pid1 (addLink pid2)
        |&gt; updateProc pid2 (addLink pid1)

addLink : Pid -&gt; Proc -&gt; Proc
addLink pid proc =
    { proc | links = proc.links |&gt; Set.insert pid }
</code></pre>

<p>And <code>Crash</code> is where we actually use <code>proc.links</code>:</p>

<pre><code class="language-elm">stepInner pid proc budget sch =
    -- ...
    stop_ : Proc -&gt; Scheduler -&gt; Scheduler
    stop_ newProc sch_ =
        sch_ |&gt; stepInner pid newProc 0
    -- ...
    Crash -&gt;
        sch
            |&gt; propagateCrashToLinks pid
            |&gt; stop_ (proc |&gt; setProgram End)

propagateCrashToLinks : Pid -&gt; Scheduler -&gt; Scheduler
propagateCrashToLinks pid sch =
    case Dict.get pid sch.processes of
        Nothing   -&gt; sch
        Just proc -&gt;
            proc.links
                |&gt; Set.foldl
                    (\linkedPid accSch -&gt;
                        accSch
                          |&gt; send linkedPid
                                  ("CRASH: " ++ String.fromInt pid)
                    )
                    sch
</code></pre>

<p>In a real-world interpreter, we’d distinguish between user messages and system messages by using an ADT, but for this toy implementation, the above will be enough.</p>

<p><a href="https://ellie-app.com/x4RjvchvjQPa1">Try it online,</a> or try the visualizer below:</p>

<script src="/assets/js/WritingYourOwnBeamDemo7.elm.js"></script>

<div class="theme_fullscreen">
    <div id="demo7" style="color: red">Oh no, the visualizer didn't load!</div>
</div>
<script>
app = Elm.WritingYourOwnBeam.Demo7.init({
    node: document.getElementById('demo7'),
});
app.ports.jumpToBottomOfTraces.subscribe((traceId) => {
    document.getElementById(traceId).scrollTop = document.getElementById(traceId).scrollHeight;
});
</script>

<p>It works in the Ellie link above, but not in the visualizer. Why? Their reduction budget is different. The Ellie demo manages to run <code>Spawn</code> and <code>Link</code> without anything else running in between, but the visualizer has reduction budget of 1, and so the child <code>Crash</code>es before the parent manages to <code>Link</code> to it.</p>

<p>This can be fixed by making the instruction pair a single atomic instruction, and BEAM does this with the <code>spawn_link</code> function. You can <a href="https://ellie-app.com/x4RjT3KBkxYa1">try it online</a> or click the <code>Fix the problem</code> button in the demo above.</p>

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

<p>And that’s all! We have implemented:</p>
<ul>
  <li>spawning child processes</li>
  <li>sending and selectively receiving messages</li>
  <li>an illusion of preemptive scheduling on top of cooperative scheduling using a reduction budget</li>
  <li>linking between processes, essentially adding hooks for when a related process stops for some reason</li>
</ul>

<p>These primitives combine together in nice ways, giving rise to BEAM’s reputation. I think they’re pretty neat, and I hope this toy implementation demystified them a little bit for you!</p>]]></content><author><name>Martin Janiczek</name></author><category term="Posts" /><summary type="html"><![CDATA[This is my Code BEAM Europe 2025 talk, converted to a blogpost.]]></summary></entry><entry><title type="html">Elm Queues Shootout!</title><link href="https://martin.janiczek.cz/2025/10/01/elm-queues-shootout.html" rel="alternate" type="text/html" title="Elm Queues Shootout!" /><published>2025-10-01T00:00:00+00:00</published><updated>2025-10-01T00:00:00+00:00</updated><id>https://martin.janiczek.cz/2025/10/01/elm-queues-shootout</id><content type="html" xml:base="https://martin.janiczek.cz/2025/10/01/elm-queues-shootout.html"><![CDATA[<p>Today’s story begins in the Elm Slack, where I saw a RSS integration post a notification about a new package:</p>

<p><a href="/assets/images/2025-10-01-elm-queues-shootout/elm-slack.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/elm-slack.png" alt="Elm Slack screenshot" /></a></p>

<p>I do love
<a href="https://en.wikipedia.org/wiki/Software_testing#Property_testing">PBT</a>-testing
data container libraries against the “spec” when given the opportunity, and
since I created myself <a href="https://martinjaniczek.gumroad.com/l/elm-bench">a small tool called
<code>elm-bench</code></a> for easier
benchmarking, I couldn’t resist and decided to find all the queue packages
currently available on <a href="https://package.elm-lang.org/">package.elm-lang.org</a>
and put them under the microscope. Let’s thoroughly test and benchmark them!</p>

<p>The rest of this blogpost shows the results of my testing and benchmarking, and
I will attempt to categorize the packages and recommend the best ones.</p>

<p>Spoiler alert: I <em>did</em> find a bug, though not in the new library that started
the whole effort!</p>

<h2 id="packages">Packages</h2>

<p>Here’s what we’ll be testing:</p>

<ul>
  <li><a href="https://package.elm-lang.org/packages/avh4/elm-fifo/1.0.4/">avh4/elm-fifo @ 1.0.4</a></li>
  <li><a href="https://package.elm-lang.org/packages/dwayne/elm-queue/1.0.0/">dwayne/elm-queue @ 1.0.0</a></li>
  <li><a href="https://package.elm-lang.org/packages/folkertdev/elm-deque/3.0.1/">folkertdev/elm-deque @ 3.0.1</a></li>
  <li><a href="https://package.elm-lang.org/packages/kudzu-forest/elm-constant-time-queue/1.4.0/">kudzu-forest/elm-constant-time-queue @ 1.4.0</a></li>
  <li><a href="https://package.elm-lang.org/packages/owanturist/elm-queue/2.0.0/">owanturist/elm-queue @ 2.0.0</a></li>
  <li><a href="https://package.elm-lang.org/packages/robinheghan/elm-deque/1.0.0/">robinheghan/elm-deque @ 1.0.0</a></li>
  <li><a href="https://package.elm-lang.org/packages/turboMaCk/queue/1.2.0/">turboMaCk/queue @ 1.2.0</a></li>
</ul>

<p>Note I have excluded
<a href="https://package.elm-lang.org/packages/francescortiz/elm-queue/1.0.0/"><code>francescortiz/elm-queue</code></a>
from the comparison because it deals with rate limiting, keyed values etc. and
is not as general-purpose as the others. One could create a generic queue out of
it but it would have severe overhead (2+ orders of magnitude).</p>

<p>On the other hand, I <em>am</em> including deques (double-ended queues) in the comparison.</p>

<h2 id="expected-api-what-is-a-queue">Expected API: what is a Queue?</h2>

<p>Queues are relatively simple: they’re a container holding 0+ items, and you can
efficiently push (enqueue) an item on one side and pop (dequeue) an item on the
other side (<a href="https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)">first in, first
out</a>).</p>

<p>Let’s expect the some variation on the following API from all of these packages:</p>

<pre><code class="language-elm">type Queue a
empty : Queue a
isEmpty : Queue a -&gt; Bool
singleton : a -&gt; Queue a
fromList : List a -&gt; Queue a
toList : Queue a -&gt; List a
enqueue : a -&gt; Queue a -&gt; Queue a
dequeue : Queue a -&gt; Maybe (a, Queue a)
length : Queue a -&gt; Int
</code></pre>

<p>For <code>length</code>, if the package doesn’t give us an “official” way, we have two
options on how to implement it: via <code>toList</code> and via repeated calls to <code>dequeue</code>
(or perhaps <code>fold</code>, if provided). The performance difference could swing both
ways, so let’s create both and measure instead!</p>

<h2 id="api-differences">API differences</h2>

<p>All of the packages indeed allow us to express the above API.</p>

<p>Here is a comparison of which functions are provided out of the box:</p>

<table>
  <thead>
    <tr>
      <th>function</th>
      <th><code>avh4</code></th>
      <th><code>dwayne</code></th>
      <th><code>folkertdev</code></th>
      <th><code>kudzu-forest</code></th>
      <th><code>owanturist</code></th>
      <th><code>robinheghan</code></th>
      <th><code>turboMaCk</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>empty</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>isEmpty</td>
      <td>❌</td>
      <td>✅</td>
      <td>✅</td>
      <td>❌</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>singleton</td>
      <td>❌</td>
      <td>❌</td>
      <td>✅</td>
      <td>❌</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>fromList</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>toList</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>enqueue</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>dequeue</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>length</td>
      <td>❌</td>
      <td>❌</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
  </tbody>
</table>

<p>Other exposed functions:</p>

<ul>
  <li><code>dwayne/elm-queue</code> (1)
    <ul>
      <li>peek</li>
    </ul>
  </li>
  <li><code>folkertdev/elm-deque</code> (11+5)
    <ul>
      <li>append, member, first, takeFront, map, map2, andMap, filter, foldl, partition, isEqualTo</li>
      <li><strong>deque-specific:</strong> pushBack, popFront, last, takeBack, foldr</li>
    </ul>
  </li>
  <li><code>kudzu-forest/elm-constant-time-queue</code> (6)
    <ul>
      <li>head, map, fold, isEqual, fromListLIFO, toListFIFO</li>
    </ul>
  </li>
  <li><code>owanturist/elm-queue</code> (34)
    <ul>
      <li>repeat, range, head, tail, take, drop, partition, unzip, any, all, member, maximum, minimum, sum, product, map, indexedMap, foldl, foldr, filter, filterMap, reverse, append, concat, concatMap, intersperse, map2, map3, map4, map5, sort, sortBy, sortWith, equals</li>
    </ul>
  </li>
  <li><code>robinheghan/elm-deque</code> (15+4)
    <ul>
      <li>initialize, repeat, range, append, left, right, dropLeft, dropRight, member, first, map, filter, filterMap, foldl, partition</li>
      <li><strong>deque-specific:</strong> pushBack, popFront, last, foldr</li>
    </ul>
  </li>
  <li><code>turboMaCk/queue</code> (5)
    <ul>
      <li>front, dropFront, map, filter, updateFront</li>
    </ul>
  </li>
</ul>

<h3 id="equality-gotcha">Equality gotcha</h3>

<p>Note that using the built-in Elm <code>==</code> equality operator on queues is unsafe for
<strong><em>ALL</em></strong> of these packages, as some values can have multiple internal
representations. The canonical example is Chris Okasaki’s queue design with two
lists, one for the rear and one for front. You could imagine two queues for
<code>singleton 1</code>: <code>Q [1] []</code> and <code>Q [] [1]</code>, and so on.</p>

<p>When working with Queue packages you need to use their provided equality
predicates, or use <code>toList</code> to find out if two queues contain the same values
in the same order.</p>

<h2 id="expected-invariants">Expected invariants</h2>

<p>Here are the properties I believe should hold for any queue. Properties having
<code>∀</code> (“for all”) qualifiers can be checked using property-based tests, properties
not having them can be checked using unit tests.</p>

<p>Note that many of these will overlap; I’ve just found it easiest to find
properties by <a href="https://www.youtube.com/watch?v=CnIlm6-XK6U">looking at pairs of
functions</a>.</p>

<p>The <code>==</code> operator below is to represent the correct way to compare two queues
for equality (see note above).</p>

<p>Here’s the types of the variables introduced in the for-alls:</p>
<pre><code class="language-elm">x : a
xs : List a
q : Queue a -- created via fromList
</code></pre>

<p>The invariants we will be checking:</p>

<ul>
  <li>empty / isEmpty
    <ul>
      <li><code>isEmpty empty == True</code></li>
    </ul>
  </li>
  <li>empty / singleton / enqueue
    <ul>
      <li><code>∀x: enqueue x empty == singleton x</code></li>
    </ul>
  </li>
  <li>empty / singleton / dequeue
    <ul>
      <li><code>∀x: dequeue (singleton x) == Just (x, empty)</code></li>
    </ul>
  </li>
  <li>empty / fromList
    <ul>
      <li><code>empty == fromList []</code></li>
    </ul>
  </li>
  <li>empty / toList
    <ul>
      <li><code>toList empty == []</code></li>
    </ul>
  </li>
  <li>empty / dequeue
    <ul>
      <li><code>dequeue empty == Nothing</code></li>
    </ul>
  </li>
  <li>empty / length
    <ul>
      <li><code>length empty == 0</code></li>
    </ul>
  </li>
  <li>isEmpty / singleton
    <ul>
      <li><code>∀x: isEmpty (singleton x) == False</code></li>
    </ul>
  </li>
  <li>isEmpty / fromList
    <ul>
      <li><code>∀xs: isEmpty (fromList xs) == List.isEmpty xs</code></li>
    </ul>
  </li>
  <li>isEmpty / toList
    <ul>
      <li><code>∀q: isEmpty q == List.isEmpty (toList x)</code></li>
    </ul>
  </li>
  <li>isEmpty / length
    <ul>
      <li><code>∀q: isEmpty q == (length q == 0)</code></li>
    </ul>
  </li>
  <li>singleton / fromList
    <ul>
      <li><code>∀x: singleton x == fromList [x]</code></li>
    </ul>
  </li>
  <li>singleton / toList
    <ul>
      <li><code>∀x: toList (singleton x) == [x]</code></li>
    </ul>
  </li>
  <li>singleton / length
    <ul>
      <li><code>∀x: length (singleton x) == 1</code></li>
    </ul>
  </li>
  <li>fromList / toList
    <ul>
      <li><code>∀xs: toList (fromList (xs)) == xs</code></li>
    </ul>
  </li>
  <li>fromList / enqueue
    <ul>
      <li><code>∀x,xs: enqueue x (fromList xs) == fromList (xs ++ [x])</code></li>
    </ul>
  </li>
  <li>fromList / length
    <ul>
      <li><code>∀xs: length (fromList xs) == List.length xs</code></li>
    </ul>
  </li>
  <li>toList / enqueue
    <ul>
      <li><code>∀x,q: toList (enqueue x q) == toList q ++ [x]</code></li>
    </ul>
  </li>
  <li>toList / length
    <ul>
      <li><code>∀q: length q == List.length (toList q)</code></li>
    </ul>
  </li>
  <li>enqueue / length
    <ul>
      <li><code>∀x,q: length (enqueue x q) == 1 + length q</code></li>
    </ul>
  </li>
  <li>length
    <ul>
      <li><code>∀q: lengthViaToList q == lengthOriginal q</code> (where applicable)</li>
      <li><code>∀q: lengthViaDequeue q == lengthOriginal q</code> (where applicable)</li>
      <li><code>∀q: lengthViaToList q == lengthViaDequeue q</code></li>
    </ul>
  </li>
</ul>

<h2 id="invariant-differences--bugs-found">Invariant differences / bugs found</h2>

<p>All tested packages behaved identically and as-expected, with the exception of
<code>owanturist/elm-queue</code>.</p>

<p>This library behaves differently wrt. <code>fromList</code> and <code>toList</code>: compared to other
libraries, they act as if they reversed the list in question:</p>

<pre><code class="language-elm">dequeue (fromList [1,2,3])
-- owanturist/elm-queue:
Just (3, queueWithout3)
-- others:
Just (1, queueWithout1)
</code></pre>

<pre><code class="language-elm">toList (enqueue 999 (singleton 1))
-- owanturist/elm-queue:
[999,1]
-- others:
[1,999]
</code></pre>

<p>Taken together, the two bugs cancel out (ie. the roundabout test “fromList / toList” doesn’t catch them), which makes them sneakier in retrospect.</p>

<p>This was submitted to the package repository as <a href="https://github.com/owanturist/elm-queue/issues/5">issue
#5</a>.</p>

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

<p>I’m using <a href="https://martinjaniczek.gumroad.com/l/elm-bench">elm-bench</a> to write
these benchmarks. It’s a tool I wrote to reduce boilerplate when using the
de-facto Elm benchmarking library,
<a href="https://package.elm-lang.org/packages/elm-explorations/benchmark/latest/"><code>elm-explorations/benchmark</code></a>.</p>

<p>Benchmarks were ran on a Macbook Pro (16-inch, Nov 2024) with the Apple M4 Pro
CPU and 48 GB RAM; Node v22.16.0 and Elm 0.19.1.</p>

<p>My setup is the following:</p>

<pre><code>.
├── v01_avh4
│   ├── elm.json
│   ├── src
│   │   └── CommonApi.elm
│   └── tests
│       └── QueueInvariants.elm
├── v02_dwayne
│   ├── elm.json
│   ├── src
│   │   └── CommonApi.elm
│   └── tests
│       └── QueueInvariants.elm
├── v03_folkertdev
│   ├── elm.json
│   ├── src
│   │   └── CommonApi.elm
│   └── tests
│       └── QueueInvariants.elm
├── v04_kudzu-forest
│   ├── elm.json
│   ├── src
│   │   └── CommonApi.elm
│   └── tests
│       └── QueueInvariants.elm
├── v05_owanturist
│   ├── elm.json
│   ├── src
│   │   └── CommonApi.elm
│   └── tests
│       └── QueueInvariants.elm
├── v06_robinheghan
│   ├── elm.json
│   ├── src
│   │   └── CommonApi.elm
│   └── tests
│       └── QueueInvariants.elm
└── v07_turboMaCk
    ├── elm.json
    ├── src
    │   └── CommonApi.elm
    └── tests
        └── QueueInvariants.elm
</code></pre>

<p>Each of the <code>CommonApi.elm</code> files contains an implementation of the, well, common API.</p>

<p>The implementations (and tests) for each tested package can be found in the
accompanying repository:
<a href="https://github.com/Janiczek/elm-queues-shootout">Janiczek/elm-queues-shootout</a>.</p>

<p>I then run the benchmarks via <a href="https://martinjaniczek.gumroad.com/l/elm-bench">elm-bench</a> in the “version” mode: this allows me
to have a separate project for each library. I can’t use them all in the same
Elm project because of module name collisions: many of the packages export a
module named <code>Queue</code>, and there can only be one.</p>

<pre><code class="language-bash">alias bench_queues="elm-bench --json -v v01_avh4 -v v02_dwayne -v v03_folkertdev -v v04_kudzu-forest -v v05_owanturist -v v06_robinheghan -v v07_turboMaCk"
</code></pre>

<p>Example usage:</p>

<pre><code class="language-bash">bench_queues CommonApi.dequeue "(CommonApi.fromList (List.range 1 5))"
bench_queues CommonApi.enqueue 1 CommonApi.empty
</code></pre>

<p><a href="https://martinjaniczek.gumroad.com/l/elm-bench">elm-bench</a> ensures the arguments to the function are precomputed (by
putting them in their own top-level declarations, which are then computed during
program initialization and before the benchmark starts). This means we <em>aren’t</em>
measuring the runtime of computing the arguments.</p>

<p>The <code>--json</code> flag gives output in a JSON form, from which the measurement can
be plucked via <code>jq ".[].nsPerRun"</code>. All measurements are in nanoseconds per run
(that is, per the measured function call).</p>

<p>Finally, “small queue/list” means <code>List.range 1 5</code> and “Large queue/list” means
<code>List.range 1 500</code>.</p>

<h3 id="measurements">Measurements</h3>

<p>I need to preface this with: this is all on a <em>nanosecond</em> scale. Don’t be
wooed by the absolute differences here - does your webapp really care about
0.2ns vs 3ns? Which operations will it do often? The <code>O(1)</code> vs <code>O(N)</code> time
complexities will probably be more instructive, though again you have to think
about the realistic sizes of your queues. Are they going to hold more than
a few hundred items?</p>

<p><a href="https://github.com/Janiczek/elm-queues-shootout/blob/main/measurements.csv">The table with the measurements is on
Github</a>,
my blog CSS is simply not up to such a gargantuan task and I can’t be bothered
to tweak it right now. CSV is more usable than a HTML table anyways!</p>

<p>Some charts (as always, click to zoom):</p>

<p><a href="/assets/images/2025-10-01-elm-queues-shootout/b01_isempty_empty.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b01_isempty_empty.png" alt="isEmpty (empty queue)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b02_isempty_small.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b02_isempty_small.png" alt="isEmpty (small queue)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b03_isempty_large.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b03_isempty_large.png" alt="isEmpty (large queue)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b04_singleton.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b04_singleton.png" alt="singleton" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b05_fromlist_empty.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b05_fromlist_empty.png" alt="fromList (empty list)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b06_fromlist_small.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b06_fromlist_small.png" alt="fromList (small list)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b07_fromlist_large.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b07_fromlist_large.png" alt="fromList (large list)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b08_tolist_empty.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b08_tolist_empty.png" alt="toList (empty queue)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b09_tolist_small.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b09_tolist_small.png" alt="toList (small queue)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b10_tolist_large.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b10_tolist_large.png" alt="toList (large queue)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b11_enqueue_empty.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b11_enqueue_empty.png" alt="enqueue (empty queue)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b12_enqueue_small.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b12_enqueue_small.png" alt="enqueue (small queue)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b13_enqueue_large.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b13_enqueue_large.png" alt="enqueue (large queue)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b14_dequeue_empty.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b14_dequeue_empty.png" alt="dequeue (empty queue)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b15_dequeue_small.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b15_dequeue_small.png" alt="dequeue (small queue)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b16_dequeue_large.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b16_dequeue_large.png" alt="dequeue (large queue)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b17_length_empty.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b17_length_empty.png" alt="length (empty queue)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b18_length_small.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b18_length_small.png" alt="length (small queue)" /></a>
<a href="/assets/images/2025-10-01-elm-queues-shootout/b19_length_large.png"><img src="/assets/images/2025-10-01-elm-queues-shootout/b19_length_large.png" alt="length (large queue)" /></a></p>

<p>Based on the limited amount of datapoints (lengths of the input list or queue),
I believe we can jot down these time complexities:</p>

<table>
  <thead>
    <tr>
      <th>test</th>
      <th><code>avh4</code></th>
      <th><code>dwayne</code></th>
      <th><code>folkertdev</code></th>
      <th><code>kudzu-forest</code></th>
      <th><code>owanturist</code></th>
      <th><code>robinheghan</code></th>
      <th><code>turboMaCk</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>isEmpty</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
    </tr>
    <tr>
      <td>singleton</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
    </tr>
    <tr>
      <td>fromList</td>
      <td><span style="color: green"><strong>O(1)</strong></span></td>
      <td><span style="color: green"><strong>O(1)</strong></span></td>
      <td>O(N)</td>
      <td>O(N)</td>
      <td>O(N) 🐛</td>
      <td>O(N)</td>
      <td><span style="color: green"><strong>O(1)</strong></span></td>
    </tr>
    <tr>
      <td>toList</td>
      <td>O(N)</td>
      <td>O(N)</td>
      <td>O(N)</td>
      <td>O(N)</td>
      <td>O(N) 🐛</td>
      <td>O(N)</td>
      <td>O(N)</td>
    </tr>
    <tr>
      <td>enqueue</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td><span style="color: red">O(logN)?</span></td>
      <td>O(1)</td>
      <td><span style="color: red">O(logN)?</span></td>
      <td>O(1)</td>
    </tr>
    <tr>
      <td>dequeue</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
      <td>O(1)</td>
    </tr>
    <tr>
      <td>length</td>
      <td>-</td>
      <td>-</td>
      <td><span style="color: green"><strong>O(1)</strong></span></td>
      <td>O(logN)</td>
      <td><span style="color: green"><strong>O(1)</strong></span></td>
      <td><span style="color: green"><strong>O(1)</strong></span></td>
      <td>O(N)</td>
    </tr>
  </tbody>
</table>

<p>It’s fascinating to see how at such small timescales, every little function
call, <code>if</code> expression and pattern match matters. There is a very visible
bimodality: the empty case behaves very differently from the two measured
non-empty cases, taking a different path through the code. Sometimes
surprisingly for the worse!</p>

<p>When a library implements <code>length</code>, it’s usually way better (in most cases,
<code>O(1)</code>) than anything you can implement yourself from <code>toList</code> or <code>dequeue</code> (where
you’re guaranteed <code>O(N)</code>).</p>

<p>And yet again, it’s hard to say how much will a hypothetical webapp feel any of
this. We’re on the scale of nanoseconds, basically almost none of this matters
unless you’re doing this in a hot loop or on huge datasets!</p>

<h3 id="length--fromlist-tradeoff">length / fromList tradeoff</h3>

<p>There is an inevitable tradeoff between <code>O(1) length + O(N) fromList</code> and <code>O(N)
length + O(1) fromList</code>, as the native Elm lists don’t hold length metadata and
thus have <code>O(N) length</code> themselves.</p>

<p>This means that to hold the precomputed number of elements in the queue
implementation (<code>O(1) length</code>), you need to count the elements during insertion:
<code>O(N) fromList</code>.</p>

<p>If you instead want to just hold the list the user gave you, without walking it
(possibly <code>O(1) fromList</code>), you’ll have to walk it in <code>length</code> to count the
elements (<code>O(N) length</code>).</p>

<p>You have to count the elements <em>somewhere</em>: on the way in or on the way out.</p>

<h3 id="categorization">Categorization</h3>

<p>It seems that there are two categories you can choose from:</p>
<ul>
  <li>Deques with somewhat rich List-like API, <code>O(1) length</code> and <code>O(N) fromList</code>
    <ul>
      <li>Both <code>folkertdev/elm-deque</code> and <code>robinheghan/elm-deque</code> fit the bill.</li>
      <li>Robin’s library seems faster at <code>fromList</code> and slower at <code>toList</code>.</li>
      <li>Robin also mentions <a href="https://github.com/robinheghan/elm-deque/tree/1.0.0?tab=readme-ov-file#differences-from-folkertdevelm-deque">possible performance
differences</a>
in his README, though I haven’t tested and measured these.</li>
    </ul>
  </li>
  <li>Queues based on Chris Okasaki’s “two lists” design, with <code>O(1) fromList</code> and
<code>O(N) length</code>
    <ul>
      <li><code>avh4/elm-fifo</code>, <code>dwayne/elm-queue</code> and <code>turboMaCk/queue</code> belong here.</li>
      <li>There are almost no differences. Dwayne’s library seems a bit slower on
<code>enqueue</code> and <code>dequeue</code> than the other two. One might prefer turboMaCk’s
library to avh4’s due to slightly richer API.</li>
      <li>A future (version of a) library could differentiate itself here by
implementing a rich List-like API.</li>
    </ul>
  </li>
</ul>

<p><code>owanturist/elm-queue</code> does have the richest API out of all the tested packages,
would otherwise belong with the other three Okasaki queues and would be my
recommended choice (if you don’t need a deque), but contains the
buggy/surprising <code>fromList</code> and <code>toList</code> behaviour.</p>

<p>I’m not completely sure where to put <code>kudzu-forest/elm-constant-time-queue</code>. The
constant-time promise for <code>enqueue</code> doesn’t seem to be there and the code needed
for worst-time guarantees is making this library slower overall, though note
that my code benchmarked one specific way of constructing a queue, and perhaps
queues that are used in a more mixed way (enqueue, dequeue, enqueue again) would
be more stable compared to other packages?</p>

<h2 id="summary">Summary</h2>

<p>We <a href="https://github.com/owanturist/elm-queue/issues/5">found a bug</a> in one of
the libraries!</p>

<p>If you need a deque, choose between <code>folkertdev/elm-deque</code> and
<code>robinheghan/elm-deque</code> based on the needed API or whether you’ll use
<code>fromList</code> or <code>toList</code> more often.</p>

<p>If you just need a queue, I recommend <code>turboMaCk/queue</code> solely based on having
slightly richer API to <code>avh4/elm-fifo</code>.</p>

<p>And if you’re one of the authors of the above libraries, adding more helper
functions would help and would make you my immediate favorite :)</p>]]></content><author><name>Martin Janiczek</name></author><category term="Posts" /><summary type="html"><![CDATA[Today’s story begins in the Elm Slack, where I saw a RSS integration post a notification about a new package:]]></summary></entry><entry><title type="html">HP M479 unofficial cartridge</title><link href="https://martin.janiczek.cz/2025/08/26/hp-m479-unofficial-cartridge.html" rel="alternate" type="text/html" title="HP M479 unofficial cartridge" /><published>2025-08-26T00:00:00+00:00</published><updated>2025-08-26T00:00:00+00:00</updated><id>https://martin.janiczek.cz/2025/08/26/hp-m479-unofficial-cartridge</id><content type="html" xml:base="https://martin.janiczek.cz/2025/08/26/hp-m479-unofficial-cartridge.html"><![CDATA[<blockquote>
  <p>TL;DR: turn off automatic updates then flash <a href="http://ftp.hp.com/pub/softlib/software13/fw-recover/M478-M479_MA/HP_Color_LaserJet_Pro_MFP_M478-M479_series_FW_002_1916A.ful2">an older firmware</a> via LPR <a href="https://www.reddit.com/r/printers/comments/tth0e4/comment/luxki98/">over the network</a>.</p>
</blockquote>

<blockquote>
  <p>TL;DR 2: Buy a <a href="https://global.brother/en">Brother</a>.</p>
</blockquote>

<p>I’m writing this post to help the next poor sod who wants to install an unofficial cartridge into their HP printer.</p>

<p>It started giving me the “Non-HP Chip Detected” error and refused to print anything from that point on.</p>

<p>All the usual tips that said I should turn the printer off, pull the plug, wait a minute and then turn it on, or that I should press the power-off button for 30s or more – none of that worked. The error was still there.</p>

<p>I tried futzing around with “Cartridge Policy” in the printer web interface (which explicitly said it would allow non-HP cartridges), and that didn’t work either.</p>

<p>In the end I opted to flashing an older firmware. I turned off automatic updates via the printer touchscreen UI, and started searching for the older firmware binaries.</p>

<p>Thanks to <a href="https://www.reddit.com/r/printers/comments/19aqimz/comment/lvfhh6a/">a Reddit post</a> I was able to find an official HP download URL (instead of some shady 3rd party website):</p>

<p><strong><a href="http://ftp.hp.com/pub/softlib/software13/fw-recover/M478-M479_MA/HP_Color_LaserJet_Pro_MFP_M478-M479_series_FW_002_1916A.ful2">http://ftp.hp.com/pub/softlib/software13/fw-recover/M478-M479_MA/HP_Color_LaserJet_Pro_MFP_M478-M479_series_FW_002_1916A.ful2</a></strong></p>

<p>Then I tried putting that on an USB stick and flashing the printer that way. Didn’t work, the back USB port resulted in the printer trying to format the flash drive, and the front USB port tried to find something to print on the drive.</p>

<p>Thanks to <a href="https://www.reddit.com/r/printers/comments/tth0e4/comment/luxki98/"><em>another</em> Reddit post</a> I was able to find an alternative way to flash the printer: through the <a href="https://en.wikipedia.org/wiki/Line_Printer_Daemon_protocol">LPR protocol</a>.</p>

<p>I needed to install the <code>lpr</code> utility via “Turn Windows Features on or off” then run the following in the commandline:</p>

<pre><code>lpr -S 192.168.8.109 -P 192.168.8.109 HP_Color_LaserJet_Pro_MFP_M478-M479_series_FW_002_1916A.ful2
</code></pre>

<p>After the command finished, it looked like nothing’s happening but then the printer restarted and started installing the firmware. After that all was done, the error was gone and my printer started printing again!</p>]]></content><author><name>Martin Janiczek</name></author><category term="Posts" /><summary type="html"><![CDATA[TL;DR: turn off automatic updates then flash an older firmware via LPR over the network.]]></summary></entry><entry><title type="html">Elm test distributions</title><link href="https://martin.janiczek.cz/2025/05/01/elm-test-distributions.html" rel="alternate" type="text/html" title="Elm test distributions" /><published>2025-05-01T00:00:00+00:00</published><updated>2025-05-01T00:00:00+00:00</updated><id>https://martin.janiczek.cz/2025/05/01/elm-test-distributions</id><content type="html" xml:base="https://martin.janiczek.cz/2025/05/01/elm-test-distributions.html"><![CDATA[<p>…in which I’ll tell you how you can make sure your property based tests <em>are</em> testing the interesting cases.</p>

<p>Recently I was discussing <a href="https://tigerbeetle.com/blog/2025-04-23-swarm-testing-data-structures/">a TigerBeetle article on swarm testing</a> with <a href="https://jfmengels.net/">Jeroen Engels</a> on the Elm Slack, and at one point, reading the paragraph:</p>

<blockquote>
  <p>For example, one weakness of our test above is that we chose to pop and push with equal probability. As a result, our queue is very short on average. We never exercise large queues!</p>
</blockquote>

<p>He asked:</p>

<blockquote>
  <p>How does one detect which situations are or aren’t covered in practice by property-based tests? Like, when would you say “the distribution we have doesn’t cover this case”?</p>
</blockquote>

<p>How do you indeed! You could use <a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Fuzz#examples"><code>Fuzz.examples</code></a> to visually check whether the generated values make sense to you:</p>

<pre><code class="language-elm">-- inside Elm REPL
&gt; import Fuzz
&gt; Fuzz.examples 10 (Fuzz.intRange 0 10)
[4,6,3,6,9,9,9,3,3,6]
    : List Int
</code></pre>

<p>but did you just get unlucky and saw no 0 and 10, or do they never get generated?</p>

<hr />

<p>To build the motivation a little bit, let’s try and see the issue from the TigerBeetle blogpost. Assume we have a Queue implementation (the details don’t matter):</p>

<pre><code class="language-elm">type Queue a
empty  : Queue a
push   : a -&gt; Queue a -&gt; Queue a
pop    : Queue a -&gt; (Maybe a, Queue a)
length : Queue a -&gt; Int
</code></pre>

<p>Now let’s try to test it!</p>

<pre><code class="language-elm">type QueueOp
    = Push Int
    | Pop

queueOpFuzzer : Fuzzer QueueOp
queueOpFuzzer =
    Fuzz.oneOf
        [ Fuzz.map Push Fuzz.int
        , Fuzz.constant Pop
        ]

applyOp : QueueOp -&gt; Queue Int -&gt; Queue Int
applyOp op queue =
    case op of
        Push n -&gt;
            Queue.push n queue

        Pop -&gt; 
            Queue.pop queue
                |&gt; Tuple.second

queueFuzzer : Fuzzer (Queue Int)
queueFuzzer =
    Fuzz.list queueOpFuzzer
        -- would generate [ Push 10, Pop, Pop, Push 5 ] etc.
        |&gt; Fuzz.map (\ops -&gt; List.foldl applyOp Queue.empty ops)
        -- instead generates a queue with the ops applied
</code></pre>

<p>The <code>queueFuzzer</code> makes a sort of random walk through the ops to arrive at a random Queue.</p>

<p>Now if we were worried we’re not testing very interesting cases, we could debug-print their lengths and look at the logs real hard and make a gut decision about whether it’s fine, but doesn’t that feel a bit icky?</p>

<p>Well, you can instead get this lovely table:</p>

<pre><code>Distribution report:
====================
  length 2-5:     37%  (370x)  ███████████░░░░░░░░░░░░░░░░░░░
  length 0:     29.6%  (296x)  █████████░░░░░░░░░░░░░░░░░░░░░
  length 1:     22.8%  (228x)  ███████░░░░░░░░░░░░░░░░░░░░░░░
  length 6-10:   9.7%   (97x)  ███░░░░░░░░░░░░░░░░░░░░░░░░░░░
  length 11+:    0.9%    (9x)  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
</code></pre>

<p>when you use <a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test#reportDistribution"><code>Test.reportDistribution</code></a> in your test:</p>

<pre><code class="language-elm">Test.reportDistribution
    [ ( "length 0",    \q -&gt; length q == 0 )
    , ( "length 1",    \q -&gt; length q == 1 )
    , ( "length 2-5",  \q -&gt; length q &gt;= 2 &amp;&amp; length q &lt;= 5 )
    , ( "length 6-10", \q -&gt; length q &gt;= 6 &amp;&amp; length q &lt;= 10 )
    , ( "length 11+",  \q -&gt; length q &gt;= 11 )
    ]
</code></pre>

<p>What’s more, you can also make the tests fail when something’s not tested enough:</p>

<pre><code>✗ Queue example 2
    Distribution of label "length 11+" was insufficient:
      expected:  10.000%
      got:       1.400%.

    (Generated 1000 values.)
</code></pre>

<p>using <a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test#expectDistribution"><code>Test.expectDistribution</code></a>:</p>

<pre><code class="language-elm">Test.expectDistribution
    [ ( Test.Distribution.atLeast 10, "length 0",    \q -&gt; length q == 0 )
    , ( Test.Distribution.atLeast 10, "length 1",    \q -&gt; length q == 1 )
    , ( Test.Distribution.atLeast 10, "length 2-5",  \q -&gt; length q &gt;= 2 &amp;&amp; length q &lt;= 5 )
    , ( Test.Distribution.atLeast 10, "length 6-10", \q -&gt; length q &gt;= 6 &amp;&amp; length q &lt;= 10 )
    , ( Test.Distribution.atLeast 10, "length 11+",  \q -&gt; length q &gt;= 11 )
    ]
</code></pre>

<hr />

<p>With all of the secrets out, let me now properly introduce you to <a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test-Distribution"><code>Test.Distribution</code></a>. It’s a relatively new addition to the Elm test library API (added in <a href="https://github.com/elm-explorations/test/blob/master/CHANGELOG.md">v2.0.0</a>, has been 3 years already, wow) which lets you measure or alternatively <em>enforce</em> how often each interesting case needs to happen.</p>

<p>This was ported over from Haskell QuickCheck (of course), where this is done with functions like <a href="https://hackage.haskell.org/package/QuickCheck-2.15.0.1/docs/Test-QuickCheck.html#v:label"><code>label</code></a> and <a href="https://hackage.haskell.org/package/QuickCheck-2.15.0.1/docs/Test-QuickCheck.html#v:checkCoverage"><code>checkCoverage</code></a>, and there’s an amazing talk <a href="https://www.youtube.com/watch?v=NcJOiQlzlXQ">“Building on developers’ intuitions to create effective property-based tests”</a> by John Hughes (of <em>course</em>) that explains the idea further.</p>

<hr />

<p>Before I get to the actual <a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test-Distribution"><code>Test.Distribution</code></a> stuff, let me also say that in addition to the <a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Fuzz#examples"><code>Fuzz.examples</code></a> mentioned earlier there’s also <a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Fuzz#labelExamples"><code>Fuzz.labelExamples</code></a> which you can use in the REPL to see an example of each labelled case (if it occurs):</p>

<pre><code class="language-elm">Fuzz.labelExamples 100
    [ ( "Lower boundary (1)",    \n -&gt; n == 1 )
    , ( "Upper boundary (20)",   \n -&gt; n == 20 )
    , ( "In the middle (2..19)", \n -&gt; n &gt; 1 &amp;&amp; n &lt; 20 )
    , ( "Outside boundaries??",  \n -&gt; n &lt; 1 || n &gt; 20 )
    ]
    (Fuzz.intRange 1 20)
--&gt;
[ ( [ "Lower boundary (1)" ],    Just 1 )
, ( [ "Upper boundary (20)" ],   Just 20 )
, ( [ "In the middle (2..19)" ], Just 3 )
, ( [ "Outside boundaries??" ],  Nothing )
]
</code></pre>

<p>As you can see, each case consists of a label and a predicate. These can overlap:</p>

<pre><code class="language-elm">Fuzz.labelExamples 100
    [ ( "fizz", \n -&gt; (n |&gt; modBy 3) == 0 )
    , ( "buzz", \n -&gt; (n |&gt; modBy 5) == 0 )
    ]
    (Fuzz.intRange 1 20)
--&gt;
[ ( [ "fizz" ],       Just 3 )
, ( [ "buzz" ],       Just 10 )
, ( [ "fizz, buzz" ], Just 15 )
]
</code></pre>

<p>You can use these classifiers in your test suites: <a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test#fuzzWith"><code>Test.fuzzWith</code></a> has a <code>distribution</code> field where you can choose between:</p>

<ul>
  <li><a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test#noDistribution"><code>noDistribution</code></a>: the default</li>
  <li><a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test#reportDistribution"><code>reportDistribution</code></a>: shows a histogram of which label happens how often</li>
  <li><a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test#expectDistribution"><code>expectDistribution</code></a>: fails the test if a labelled case doesn’t happen as specified:
    <ul>
      <li><a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test-Distribution#atLeast"><code>atLeast</code></a>: N% of the time or more</li>
      <li><a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test-Distribution#zero"><code>zero</code></a>: never</li>
      <li><a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test-Distribution#moreThanZero"><code>moreThanZero</code></a>: at least once</li>
    </ul>
  </li>
</ul>

<hr />

<p>Let’s see some more examples. <a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test#reportDistribution"><code>Test.reportDistribution</code></a> used in the following way:</p>

<pre><code class="language-elm">Test.fuzzWith
    { runs = 10000
    , distribution =
        Test.reportDistribution
            [ ( "fizz", \n -&gt; (n |&gt; modBy 3) == 0 )
            , ( "buzz", \n -&gt; (n |&gt; modBy 5) == 0 )
            , ( "even", \n -&gt; (n |&gt; modBy 2) == 0 )
            , ( "odd",  \n -&gt; (n |&gt; modBy 2) == 1 )
            ]
    }
    (Fuzz.intRange 1 20)
    "Fizz buzz even odd"
    (\n -&gt; Expect.pass)
</code></pre>

<p>will show the following histogram:</p>

<pre><code>Distribution report:
====================
  even:             50.2%  (5017x)  ███████████████░░░░░░░░░░░░░░░
  odd:              49.8%  (4983x)  ███████████████░░░░░░░░░░░░░░░
  fizz:             30.1%  (3011x)  █████████░░░░░░░░░░░░░░░░░░░░░
  buzz:             19.2%  (1924x)  ██████░░░░░░░░░░░░░░░░░░░░░░░░

Combinations (included in the above base counts):
  fizz, even:       15.2%  (1524x)  █████░░░░░░░░░░░░░░░░░░░░░░░░░
  fizz, odd:        10.1%  (1013x)  ███░░░░░░░░░░░░░░░░░░░░░░░░░░░
  buzz, even:        9.5%   (949x)  ███░░░░░░░░░░░░░░░░░░░░░░░░░░░
  buzz, odd:           5%   (501x)  ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  fizz, buzz, odd:   4.7%   (474x)  █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
</code></pre>

<p>As you would expect, of the 20 numbers in the range <code>1..20</code>,</p>

<ul>
  <li>there are 10 even and 10 odd ones
    <ul>
      <li>the labels <code>even</code> and <code>odd</code> should happen with probability 10/20 (50% of the time), though the real counts will vary slightly due to randomness</li>
    </ul>
  </li>
  <li>there are 6 multiples of 3
    <ul>
      <li>the label <code>fizz</code> should happen with probability 6/20 (30% of the time)</li>
    </ul>
  </li>
  <li>there are 4 multiples of 5
    <ul>
      <li>the label <code>buzz</code> should happen with probability 4/20 (20% of the time)</li>
    </ul>
  </li>
</ul>

<blockquote>
  <p>Note the combinations are disjoint in a sense: the hits for <code>fizz, buzz, odd</code> <em>aren’t</em> counted in <code>fizz, odd</code> and that’s why <code>fizz, odd</code> only shows around 10% probability instead of the expected 15%: <code>fizz, buzz, odd</code> has stolen the missing 5% from it as a more specific combination of labels.</p>
</blockquote>

<hr />

<p>Distributions are more useful when you enforce them instead of just reporting them. Use <a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test#expectDistribution"><code>Test.expectDistribution</code></a>:</p>

<pre><code class="language-elm">Test.fuzzWith
    { runs = 100
    , distribution =
        Test.expectDistribution
            [ ( Test.Distribution.atLeast 4,    "low",        \n -&gt; n == 1 )
            , ( Test.Distribution.atLeast 4,    "high",       \n -&gt; n == 20 )
            , ( Test.Distribution.atLeast 80,   "in between", \n -&gt; n &gt; 1 &amp;&amp; n &lt; 20 )
            , ( Test.Distribution.zero,         "outside",    \n -&gt; n &lt; 1 || n &gt; 20 )
            , ( Test.Distribution.moreThanZero, "one",        \n -&gt; n == 1 )
            ]
    }
    (Fuzz.intRange 1 20)
    "Int range boundaries - mandatory"
    (\n -&gt; Expect.pass)
</code></pre>

<p>In the test above, we expect the uniform fuzzer of numbers 1..20 to produce the number 1 at least 4% of the time. If the real probability was 2%, the test would fail on grounds of distribution, even though the actual test function always passes.</p>

<blockquote>
  <p>In reality the number 1 will happen 5% of the time (1/20; <a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Fuzz#intRange"><code>Fuzz.intRange</code></a> is uniform), but it’s not the best idea to enforce the exact probability that will happen, because the library tries to run the fuzzer until it’s statistically sure (1 false positive in 10<sup>9</sup> runs) that the distribution is reached.</p>

  <p>This means that instead of the default 100 fuzzed values it might end up generating thousands or millions of values to make sure. So being a bit off the real probability helps keep the test suite fast.</p>
</blockquote>

<p><a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test#expectDistribution"><code>Test.expectDistribution</code></a> won’t show the table and will generally be silent, but it will complain loudly and fail the test if the wanted distribution isn’t reached (even if the actual test function passes), like in the following example where I’ve bumped the expected probability of generating the number 1 to 10%:</p>

<pre><code>✗ Int range boundaries - mandatory
    Distribution of label "low" was insufficient:
      expected:  10.000%
      got:       5.405%.

    (Generated 2146 values.)
</code></pre>

<p>You can see it generated 2146 values to be sure of the result, instead of the specified 100.</p>

<hr />

<p>That about covers it! This post mostly wants to show that this <em>can be done</em> in the Elm PBT testing world; if you want to dive deeper I heartily recommend the mentioned <a href="https://www.youtube.com/watch?v=NcJOiQlzlXQ">YouTube talk</a> by John Hughes.</p>

<p>TL;DR: with <a href="https://package.elm-lang.org/packages/elm-explorations/test/2.2.0/Test-Distribution"><code>Test.Distribution</code></a> you can measure and enforce how often do your tests generate categories of values of your choosing.</p>]]></content><author><name>Martin Janiczek</name></author><category term="Posts" /><summary type="html"><![CDATA[…in which I’ll tell you how you can make sure your property based tests are testing the interesting cases.]]></summary></entry><entry><title type="html">Taking my diabetes treatment into my own hands</title><link href="https://martin.janiczek.cz/2024/07/23/taking-my-diabetes-treatment-into-my-own-hands.html" rel="alternate" type="text/html" title="Taking my diabetes treatment into my own hands" /><published>2024-07-23T00:00:00+00:00</published><updated>2024-07-23T00:00:00+00:00</updated><id>https://martin.janiczek.cz/2024/07/23/taking-my-diabetes-treatment-into-my-own-hands</id><content type="html" xml:base="https://martin.janiczek.cz/2024/07/23/taking-my-diabetes-treatment-into-my-own-hands.html"><![CDATA[<p>First of all, this blogpost is kinda long. Let me prove to you reading it <em>will</em> actually have some payoff:</p>

<p><a href="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/04-original-and-best.png"><img src="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/04-original-and-best.png" alt="See, I wrote something!" /></a></p>

<p>OK, now that you’ll stay, let’s start from the beginning…</p>

<hr />

<p>I’m a Type 1 diabetic. This means my pancreas doesn’t produce insulin (which allows cells to use blood glucose for energy) and I have to provide it externally.</p>

<p>This is a finnicky process, because you need to balance your glucose in the right “zone” - not too high (hyperglycemia, &gt;10 mmol/l, is a long-term danger to your body) and not too low (hypoglycemia, &lt;4 mmol/l, is a short-term danger to your body). If it’s too high, you need to inject insulin, and if it’s too low, you need to eat some sugars.</p>

<p>A commonly used metaphor for this is flying a plane. There are games illustrating the process as well: click blue button, bird flies down, click orange button, bird flies up. Too high bad, too low bad, just right good.</p>

<p><a href="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/icarus.png"><img src="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/icarus.png" alt="A game showing the process" /></a></p>

<p>The issues with manually managing this process you’ve inherited from your douchebag pancreas are manifold:</p>

<ul>
  <li>the insulin doesn’t act immediately, there’s around 20min delay (depending on the brand) before your blood glucose goes down</li>
  <li>the <em>food</em> doesn’t act immediately, there’s around 20min delay (depending on the food!) before your blood glucose goes up
    <ul>
      <li>simple sugars (eg. fruit) are faster (and stop acting faster as well)</li>
      <li>complex sugars (rice / potatoes / …) are slower. You usually want your blood glucose as constant and flat as possible. The worst thing you can do is to go from hypo to hyper to hypo to hyper in huge amplitude swings.</li>
      <li>fats also somehow affect the digestion of sugars. I can’t be bothered to remember how, I just ignore them honestly.</li>
    </ul>
  </li>
  <li>injecting insulin ~15min before you start eating would do <em>wonders</em> for neutralizing the BG spike, the issue is, nobody does it, because what if you then get a smaller serving at the restaurant or it gets delayed? What if you get called somewhere urgently and can’t finish your meal? People usually inject right before the meal / after the meal as a result.</li>
  <li>there’s no generic formula (that I know of) for estimating how much will a gram of sugar increase your blood glucose, nor how much will an unit of insulin decrease your blood glucose. <em>It’s all vibes.</em></li>
  <li>mathematical models <em>do</em> exist but you need to find your body’s parameters - I’ll get to it below!</li>
  <li>the body has its own emergency reserves of glucose, which it can sometimes decide to use (though beware, this system turns off when you’re drunk), so <em>maybe</em> the Snickers bar you just ate to save your life wasn’t actually needed anymore, and you end up with a hyper</li>
  <li>you’re not quite yourself during a hypo (you get slower, dumber, I’ve heard of people getting stuck in thought loops in front of an open fridge), and so even though you intellectually know you just ate enough to get back into the correct levels <em>eventually</em>, your brain is screaming at you <strong>“EAT! YOU’RE DYING! I AM LOW ON SUGAR <em>NOW!!</em>“</strong> and in my case this leads to overcompensating quite knowingly and willingly. <em>Yeah one more yoghurt can’t hurt.</em> Well…</li>
  <li>insulin doesn’t work when eaten, you need to inject yourself with it (though nowadays we do it under skin, not into veins, I sure am glad I’m not living 50 years ago) or inhale it. Since injections aren’t the most pleasant thing in the world, the dosage is usually limited to 4x a day (to counteract the three main meals + a long-acting different type of insulin once a day), even though you’d be more stable if you injected more often, with smaller doses.
    <ul>
      <li>(no experience with inhalations here, so I’ll skip this)</li>
      <li>(insulin pumps do exist, I’ll mention those briefly in a moment… maybe)</li>
    </ul>
  </li>
  <li>measuring your blood glucose level is painful if you are using test strips and need to prick your fingers to provide a blood drop, so measuring your sugar is usually limited to 4-6x a day – again, even though it would be better to have more data points.
    <ul>
      <li>this is less of a problem nowadays with Continuous Glucose Monitoring systems like Freestyle Libre, which you install into your arm once every 14 days and get a measurement every minute through Bluetooth to your phone</li>
    </ul>
  </li>
  <li>things like physical effort, illness, stress, <em>heck, even seasons of the year</em> do all affect how your body behaves and reacts to sugar / insulin</li>
  <li>there’s this damn thing called <a href="https://en.wikipedia.org/wiki/Dawn_phenomenon">dawn phenomenon</a> that some diabetics, me included, experience: in the morning your sugar will just start going up and up. If you wanted to sleep in on the weekend, well tough luck, you’re now in the 15 mmol/l range.</li>
</ul>

<p>I hope this incomplete list gives you an idea of how wonky the process of trying to make your blood glucose stay in the right levels is.</p>

<div style="width: 50%; margin: 0 auto">
<a href="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/phone.jpeg">
<img src="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/phone.jpeg" alt="My 7-day average" />
</a>
</div>

<p>My treatment is usually: keep the Freestyle Libre app on my phone open as much as possible and when I see my BG’s getting high, I inject a small amount of insulin. How much? No idea. <em>IT’S ALL VIBES.</em></p>

<p>But sometimes the app is yelling at you: “you’re at 15 mmol/l for an hour now, idiot!” and you just don’t (want to) pay attention. Alert fatigue is a real thing.</p>

<p>I have some recommended insulin dosages that we’ve settled on with my diabetologist, whom I visit every three months. So my regimen can look like:</p>

<ul>
  <li>Breakfast: inject 18 units, eat 24g of sugars</li>
  <li>Lunch: inject 22 units, eat 60g of sugars</li>
  <li>Dinner: inject 21 units, eat 60g of sugars</li>
  <li>Before sleep: inject 32 units of long-acting insulin</li>
</ul>

<p>And then I see my diabetologist, she looks at the 7d / 14d / 30d averages and says “maybe you can try fixing the 15:00 hypers you’re getting quite regularly, by injecting more insulin before lunch. You should also lose weight, when you started coming here you had 80kg, now you’re a centurion. Like seriously, WTF. OK cool bye, see you in 3 months!”</p>

<p>Lovely.</p>

<p>If you can’t tell, the thing that irks me the most about the whole thing is: <em><span class="zalgo">Ï̷̛̛̮̏̀̊͠T̵̨̡̏͝’̵̧͍̐̂̑̈́͐̐͜S̸͉̖͒̈̀̕͝ ̴̢̺̤̜͎͚̙́Ạ̷͕̱͖͙̉̊L̴̼̞̺̤̞̬̟̅̈́L̷̠̔̏̐̃̚ ̴̞̊͛͝V̸͇͚̱͑̄̈̌̊Ḯ̴̧̧͚̰̞̈́͝B̸̡̬̪͊̌͂̓̐E̵͙̼̰̞̹͇̎̽̈́̓Ş̷̱͍͖̼͍̯̉̾͊̂̾͝</span>.</em> I’d seriously appreciate it if my diabetologist used a model, or a simulation of some kind, got my body’s parameters there somehow, and found the improvements to my schedule <em>that way</em>. Maybe she has some expert knowledge in her head but from my perspective it’s all guessworks. Err, I mean <span class="zalgo">v̴̼̂i̴̥̇b̸̠̌e̶͙̕ś̴̲</span>.</p>

<p>And this is where my programmer mind comes in.</p>

<hr />

<p>There are people who take <strong>insulin pumps</strong> (which provide insulin in very small very frequent doses and are ~permanently injected into your body, but are otherwise dumb as a brick) and combine them with <strong>continuous glucose monitors,</strong> and make the glucose measurements inform and control the pump. This is called “closed loop” or “artificial pancreas”, and getting one officially is very hard or impossible: not FDA approved yet / you need to be part of an university study to get one / … It’s one of those things that “will be here in 5 years”, <em>they say every year for the past 30 years.</em></p>

<blockquote>
  <p>Aside: I try not to be too butthurt about it: CGMs have just recently started being available and even fully sponsored by the Czech health insurance companies, and having a 1440-datapoints-a-day graph is a <em>MASSIVE</em> improvement compared to pricking your finger 4x a day and getting 4 datapoints for your blood glucose graph with nothing in between. So, the artificial pancreas is slowly coming. Unlike nuclear fusion.</p>
</blockquote>

<p>The most prominent of these people hacking their devices together, in my social bubble at least, is <a href="https://www.hanselman.com/">Scott Hanselman</a>, the Microsoft programmer guy. (Check out his <a href="https://www.youtube.com/watch?v=uNhYhlBQoEY">talk</a>.) He’s a T1DM as well and has been promoting the <a href="https://wearenotwaiting.net/">#WeAreNotWaiting</a> initiative where people take their own pump and their own CGM and hack them together despite the healthcare companies’ pleas that it’s not approved and not safe etc. #TheyAreNotWaiting.</p>

<p>And that is really inspirational.</p>

<p>I don’t have a pump myself (and to have a chance of getting one I’d first have to find another diabetologist, which makes this into a <em>“too much work, can’t be bothered”</em> issue for me), so I can’t currently do quite what they are doing, but I can go with the high-level idea and #NotWait in my own way.</p>

<hr />

<p>A few days ago I was fumbling down the stairs to our kitchen at ~3:00 in the morning to fix my hypo. (Night hypoglycemias are <em>especially</em> bad: what if you don’t wake up?) On the stairs I had the thought: why the hell is there no app into which I’d put my past X blood glucose values, my usual daily schedule, my weight, height, gender, age, whatever, and it would let me play with some kind of prediction (interactively!) and find good dosages / meal times / injection times? Then I would have a potentially good target to get to, and over the course of a few days I could gradually adjust my real dosage to that level and see how it behaves, and hopefully stay in the 4-10 mmol/l range much more easily.</p>

<p>Why doesn’t such a thing exist?</p>

<p>(Coincidentally I’ve also started chatting about this and other app ideas with <a href="https://twitter.com/lambdapriest">John Pavlick</a>. Thanks for your encouragement, John!)</p>

<p>So I started googling. Turns out there are <em>many</em> papers detailing differential equations for a model of how glucose and insulin interact, and how an artificial pancreas could get you to the correct range automatically. The issue is, I DON’T HAVE AN ARTIFICIAL PANCREAS. I have four daily injections. Give me something useful for those!</p>

<p>There’s not too much to pick from.</p>

<blockquote>
  <p>To be honest, I also don’t really know how to translate those differential equations into a simulation algorithm, even if I found a good model. I’m guessing I need to write an <a href="https://en.wikipedia.org/wiki/Ordinary_differential_equation">ODE</a> solver like <a href="https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods">Runge-Kutta</a> for the specific set of equations and somehow find <em>my</em> specific parameters for it. It’s been a while since I studied this in uni.</p>
</blockquote>

<p>One of the links led me to <a href="https://diabetes.zcu.cz/">diabetes.zcu.cz</a> though, and in particular their <a href="https://diabetes.zcu.cz/smartcgms/">SmartCGMS</a> app (open-source too!). From the screenshots it seemed kinda relevant, or at least similar to what I had in mind for my dream app.</p>

<p><a href="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/smartcgms.png"><img src="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/smartcgms.png" alt="SmartCGMS" /></a></p>

<p>So I sent an email to the authors. Found one maintainer on GitHub and wrote them an email detailing my woes and the app I’d love to write, and whether they could point me to some existing software or good papers on modelling multiple-dose-injection treatment (as opposed to a pump / artificial pancreas).</p>

<p>What they gave me was better than I could imagine. (Thanks again, <a href="https://github.com/MartinUbl">Martin</a>!) They had a wrapper for the core SmartCGMS engine (which contains <a href="https://github.com/SmartCGMS/core/blob/dffdd89a274144d0e9ecbe9f581db9eca0e4b8ed/model/src/bergman/bergman.cpp#L82">implementations of some of these models</a> already) for the C# language. The API was quite simple:</p>

<ul>
  <li><code>.Create(...)</code> - initialize the simulation</li>
  <li><code>.Step()</code> - step one time-unit (typically a minute) in the simulation</li>
  <li><code>.ScheduleInsulinBasalRate(double unitsPerHour)</code> - schedule a (pump, sadly) insulin dosage</li>
  <li><code>.ScheduleInsulinBolus(double units)</code> - schedule a short-acting insulin injection</li>
  <li><code>.ScheduleCarbohydratesIntake(double grams)</code> - schedule food consumption</li>
  <li><code>.Terminate()</code> - stop the simulation</li>
</ul>

<p>And then there was the current simulation state:</p>

<ul>
  <li><code>.BloodGlucose</code> - current blood sugar, in mmol/l</li>
  <li><code>.InterstitialGlucose</code> - glucose in your interstitial fluid - let’s skip it, not important</li>
  <li><code>.InsulinOnBoard</code> - how much insulin is still left to be absorbed</li>
  <li><code>.CarbohydratesOnBoard</code> - how much sugar is still left to be absorbed</li>
</ul>

<p>As you can see, with some caveats this would let me make a simulation for my daily schedule.</p>

<p>So a few moments later I had something like this (in C# but here presented as Elm)</p>

<pre><code class="language-elm">type IntakeType
  = BasalInsulin -- long-acting
  | BolusInsulin -- short-acting
  | Carbs        -- fooooooooooooood!

type alias Intake =
  { intakeType : IntakeType
  , amount : Float
  , timeMinutes : Int
  }

type alias Input =
  { basalInsulin : Intake
  , bolusInsulins : List Intake
  , carbs : List Intake
  }

type alias OutputRow =
  { minute : Float
  , bloodGlucose : Float
  , carbohydratesOnBoard : Float
  , insulinOnBoard : Float
  , interstitialGlucose : Float
  }

simulate : Input -&gt; Int -&gt; List OutputRow
simulate input days =
  ...

mySchedule : Input
mySchedule =
  { basalInsulin = Intake BasalInsulin (22 * 60) 32
  , bolusInsulins =
      [ Intake BolusInsulin (10 * 60) 18
      , Intake BolusInsulin (13 * 60) 22
      , Intake BolusInsulin (19 * 60) 21
      ]
  , carbs =
      [ Intake Carbs (10 * 60) 24
      , Intake Carbs (13 * 60) 60
      , Intake Carbs (19 * 60) 60
      , Intake Carbs (22 * 60) 24
      ]
  }

myPrediction : List OutputRow
myPrediction =
  simulate mySchedule 3
</code></pre>

<p>Never have I copied the resulting CSV into Google Sheets so fast. Tada:</p>

<p><a href="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/00-accidental-art.png"><img src="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/00-accidental-art.png" alt="I'm the artist now." /></a></p>

<p>Oh, wait, that’s not it. Cool piece of accidental art though! Now let me use the correct column for the X axis.</p>

<p><a href="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/01-google-sheets.png"><img src="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/01-google-sheets.png" alt="Google Sheets" /></a></p>

<p>Well hot damn. The graph actually makes sense!</p>

<p>Granted, it’s not <em>me</em> in the graph but I can simulate <em>someone</em> now!</p>

<p><em>Let’s stash away “I need to actually simulate</em> me, <em>you know” as a TODO for future Martin and continue.</em></p>

<hr />

<p>Next I made a Windows Forms application with an OxyPlot chart so that I don’t need to write a CSV to a file and manually copy it to Google Sheets.</p>

<blockquote>
  <p>Aside: what do you .NET folks use nowadays? MAUI? WPF? Xamarin Forms? It’s kinda confusing for an outsider.</p>
</blockquote>

<p>This took me a while to figure out (I’m not a C# guy; honestly I’ve thought about rewriting this into F# instead the moment I had to start learning about event handlers and delegates), but I succeeded:</p>

<p><a href="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/02-initial-graph.png"><img src="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/02-initial-graph.png" alt="Initial graph" /></a></p>

<p>There’s one little issue with seeing just the first day of the simulation though, and that’s the fact that I inject my basal (long-acting) insulin at 22:00. So for the majority of the first day the glucose will just be higher because I haven’t injected the long-acting insulin yet. So let’s simulate more days just to see what happens.</p>

<p><a href="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/03-ranges.png"><img src="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/03-ranges.png" alt="Ranges" /></a></p>

<p>Oh wow, it gets periodic by day 3! Cool!</p>

<p>As you can see I’ve also added the hypo- and hyperglycemic ranges so that it’s clearer where the “Goldilocks zone” lies. As it turns out, my insulin dosage is absolutely inappropriate for the person simulated by SmartCGMS right now. Hitting such severe hypoglycemia, they’d probably be dead by now (or their liver has to work overtime on dosing that emergency glucose).</p>

<p>OK, well then, now it’s just a small step from simulating a hardcoded</p>

<pre><code class="language-elm">mySchedule : Input
mySchedule =
  { basalInsulin = Intake BasalInsulin (22 * 60) 32
  , bolusInsulins =
      [ Intake BolusInsulin (10 * 60) 18
      , Intake BolusInsulin (13 * 60) 22
      , Intake BolusInsulin (19 * 60) 21
      ]
  , carbs =
      [ Intake Carbs (10 * 60) 24
      , Intake Carbs (13 * 60) 60
      , Intake Carbs (19 * 60) 60
      , Intake Carbs (22 * 60) 24
      ]
  }
</code></pre>

<p>to automatically finding a more optimal dosage!</p>

<hr />

<p>I’ve opened the NuGet package manager, wrote <code>genetic</code> and installed the most popular package: <a href="https://github.com/giacomelli/GeneticSharp">GeneticSharp</a>. Turns out it’s <em>really solid.</em> It needed me to provide the usual stuff: chromosomes, crossover, selection, population size, termination criteria… and the fitness function.</p>

<p>The fitness function actually deserves a fuller description. It looks like this:</p>

<pre><code class="language-elm">fitness : Input -&gt; Float
fitness input =
  let
    output : List OutputRow
    output = simulate input 3

    longtermHypos = output |&gt; List.takeLast (24*60) |&gt; List.count (\row -&gt; row.bloodGlucose &lt; 4)
    longtermHypers = output |&gt; List.takeLast (24*60) |&gt; List.count (\row -&gt; row.bloodGlucose &gt;= 10)
    ...

    longtermHyposNormalized = longtermHypos / List.length output
    longtermHypersNormalized = longtermHypers / List.length output
    ...

    longtermHyposWeight = 15
    longtermHypersWeight = 12
    ...

    weightSum = longtermHyposWeight + longtermHypersWeight + ...
  in
  ( longtermHyposNormalized * longtermHyposWeight
  + longtermHypersNormalized * longtermHypersWeight
  + ...
  ) / weightSum
</code></pre>

<p><em>(Yes I know stuff is computed needlessly here; in the real C# code I’m doing all the intermediate result reuse you wish I did here, but I’m optimizing for understanding instead here.)</em></p>

<p>Turns out I care about many things. The following list evolved gradually but I’m only giving you the final version:</p>
<ul>
  <li>minimize # of hypoglycemic readings in the (stabilized) last day</li>
  <li>minimize # of hyperglycemic readings in the last day</li>
  <li>as small amplitude between min and max glucose reading as possible in the last day</li>
  <li>minimize the sum of bolus insulin dosages</li>
  <li>minimize the basal insulin dosage</li>
  <li>minimize # of hypoglycemic readings in the stabilization phase (first 2 days)</li>
  <li>minimize # of hyperglycemic readings in the stabilization phase</li>
</ul>

<p>Note that I don’t care about all of those equally. I’ve actually sorted the above list by priority: long-term hypos are the most urgent, the temporary hypers while the system settles I care about the least. I’m encoding that via the weights. Each of the normalized measurements is a number <code>0..1</code>, which then gets multiplied by the weight.</p>

<p>I’m not quite sure whether this is the right way to encode multiple concerns into a single number, but it’s the best I could come up with without consulting math books, and it seems to work well. At least I couldn’t find a case where an input with lower (better) fitness was less preferable to me (according to my brain’s fuzzy intuition) than another input with higher (worse) fitness.</p>

<p>If I was able to construct all the values and sort them, I’d probably do something like</p>

<pre><code class="language-elm">allCombinations
  |&gt; List.sortBy (\output -&gt;
       [ longtermHypos output
       , longtermHypers output
       , amplitude output
       , ...
       ]
     )
</code></pre>

<p>(that is, I can order outputs pairwise), but that’s not how the genetic programming libraries work. I believe explicitly not going through the whole space is one of their very top priorities :)</p>

<blockquote>
  <p>Aside: how many inputs are there? This to me looks like permutations with repetitions (order does matter), <code>n^r</code> , so in my case <code>injectionPossibilities ^ injectionCount</code>, and for my specific example schedule, <code>51^4</code>  (assuming I can inject <code>0..50</code> units of insulin). That’s around 6.7 million.</p>
</blockquote>

<p>So this could be bruteforceable. But I have to run the whole simulation inside the fitness function, and it takes around a second or two.</p>

<p>At least it’s parallelizable then! (And believe me, I’m making use of my 16 cores.)</p>

<p>Given it’s a pure function (basically… the results vary around the 10th decimal digit - negligible), I can memoize the fitness function for the input of four ints. The genetic algorithm ends up repeating some guesses quite a lot near the end of the simulation, so this actually saves a lot of time.</p>

<p>Writing a memoization function in C# was a breath of fresh air, coming from the pure FP world of Elm:</p>

<pre><code class="language-c">private Dictionary&lt;List&lt;Intake&gt;, double&gt; fitnessCache = new(new IntakesSameAmount());

// ...

if (fitnessCache.TryGetValue(amounts, out var cachedFitness))
    return cachedFitness;

// ...

var fitness = Fitness(newInput);
fitnessCache.Add(amounts, fitness);
return fitness;
</code></pre>

<p><em>Amazing what you can do with a bit of mutation. Yeah I’ll go return my Elm badge now.</em></p>

<hr />

<p>So, with this fitness function created, I can now run the genetic algorithm. Let’s add a button and some more info to the UI, and start it off!</p>

<p><a href="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/04-original-and-best.png"><img src="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/04-original-and-best.png" alt="Intakes on the side, and a button" /></a></p>

<p><em>WELL HOT DAMN.</em> It has optimized the insulin intakes and the patient (again, sadly not <em>me</em>) is now stable inside the 4-10 mmol/l range.</p>

<p>That’s really magical. I feel like I’ve solved diabetes. Of course it’s not as simple. There will always  be unexpected things and changes you need to react to. You’ll have hypers, you’ll have hypos and it’s OK. But still. It managed to squeeze the blood glucose into the correct range.</p>

<hr />

<p>So, what do I need to make this useful <em>to me?</em></p>

<p>I feel like interactivity would go a long way. Being able to add injections or meals, move them up and down, left and right, and seeing the graph change based on that, would give the diabetic much better understanding than the <em>“OK I’m gonna inject more before lunch today and see what happens two hours from now”</em> feedback loop, or even worse, the <em>“consult a doctor every three months”</em> one. Did I mention I’m a big fan of short feedback loops?</p>

<p>Of course, it needs to simulate <em>me</em> instead of somebody else. I can’t use these optimized dosages because my body reacts differently. I need to consult this with the SmartCGMS folks, but the process will likely involve me downloading my historical blood glucose data off my Freestyle Libre sensor and somebody somewhere fitting the model parameters to that data. The math escapes me but it can be done.</p>

<p>Another issue is that the algorithm is optimized for pumps, and my basal (long-acting) insulin will need to be tracked into the model a bit differently. Right now it’s as if I was injecting 1/24-th of the dosage every hour, instead of the full dosage once a day. But once the specific insulin brand and its behaviour is tracked in the software, I will be able to call <code>.ScheduleInsulinBasal(double units)</code> and all should be well (and more precise), hopefully.</p>

<p>So, there’s a bunch of stuff yet to be done and collaborate with the SmartCGMS folks on, but I’m <em>really</em> excited by this, and feel empowered taking care of my diabetes better than I thought I could. WE’VE GOT THE TECHNOLOGY! Genetic algorithms and Markov Chains, baby!</p>

<hr />

<p>What’s that? I didn’t mention Markov Chains <em>once</em> in the article?</p>

<p>Oh yeah, well, I experimented with a bunch of stuff. To end off the blogpost, here’s a stupid random walk arriving at a value iteratively. (Change each intake by a random value <code>-2..+2</code>, and if the fitness of that tweaked input is better, keep the change, otherwise rollback.)</p>

<video src="/assets/images/2024-07-23-taking-my-diabetes-treatment-into-my-own-hands/recording.mp4" style="width: 100%" controls="">
</video>]]></content><author><name>Martin Janiczek</name></author><category term="Posts" /><summary type="html"><![CDATA[First of all, this blogpost is kinda long. Let me prove to you reading it will actually have some payoff:]]></summary></entry><entry><title type="html">Notes from Elm Camp 2024</title><link href="https://martin.janiczek.cz/2024/06/22/notes-from-elm-camp-2024.html" rel="alternate" type="text/html" title="Notes from Elm Camp 2024" /><published>2024-06-22T00:00:00+00:00</published><updated>2024-06-22T00:00:00+00:00</updated><id>https://martin.janiczek.cz/2024/06/22/notes-from-elm-camp-2024</id><content type="html" xml:base="https://martin.janiczek.cz/2024/06/22/notes-from-elm-camp-2024.html"><![CDATA[<p><a href="/assets/images/2024-06-22-notes-from-elm-camp-2024/IMG_20240618_201509638.jpg"><img src="/assets/images/2024-06-22-notes-from-elm-camp-2024/IMG_20240618_201509638.jpg" alt="1" /></a></p>

<p>I’m writing this on the plane from London to Prague, as we’re waiting for delayed takeoff due to thunderstorms over Czech Republic. (EDIT: A day later, I’m now finishing this on my couch back in Ostrava.)</p>

<p>I’ve spent an amazing week in the English countryside, among roughly 40(?) Elm-minded folks. Many of them are people I interact with almost daily on the Elm Slack or the Incremental Elm Discord, but for many of them this was the first time I could assign a face to a name! Same with my coworkers, John and Wolfgang. This alone made the event very special. Say what you want about online communication tools, in my mind meeting in person will always be vastly superior, at least in the friendship-forming aspect.</p>

<p>The venue and surrounding nature were breathtaking, the food was delicious, the accomodation was comfortable, the travel… we don’t speak about the travel.</p>

<p><a href="/assets/images/2024-06-22-notes-from-elm-camp-2024/IMG_20240619_095622515.jpg"><img src="/assets/images/2024-06-22-notes-from-elm-camp-2024/IMG_20240619_095622515.jpg" alt="2" /></a></p>

<p>The venue had a piano, which I appreciated very much. I like being around people without neccessarily having to hold a conversation, and this was one way to do that. Did you know there are a bunch of talented musicians in the Elm community? Mario was my partner in crime in improvising Coldplay stuff by ear, Rupert wowed me with his ability to read sheet music, Georges and Leonardo are awesome singers and I just couldn’t stop smiling when everybody joined in. James showed us his compositions, because of course he’s composing. Why am I even surprised. Leo showed off a bit of his dancing. Did you know Mark can draw like nobody’s business? Shout out also to one moment of kindness in particular: Mario stopping our round robin of who will play the piano next and instead taking the role of a mentor and teaching Janine the “anybody can play the piano: black keys are a pentatonic scale and will sound good together no matter which ones you choose” trick. Made me think about selfishness and unselfishness and actively bringing people into your circle and I appreciate Mario all that more for it.</p>

<p>On the music note, I also had a synthy jam with Georges, but we both kept trying to master our instruments enough to get something nice going on, and didn’t succeed on the first try. I’m sure we’ll try again one day and sound better :)</p>

<p><a href="/assets/images/2024-06-22-notes-from-elm-camp-2024/IMG_20240619_105619108.jpg"><img src="/assets/images/2024-06-22-notes-from-elm-camp-2024/IMG_20240619_105619108.jpg" alt="3" /></a></p>

<p>We had a bonfire on two occassions, which was very nice. I had some great conversations with Jeroen and Janine about parenthood and the varying social support for parents in different countries. (Also, I’ve been told the Japanese version of Love Is Blind is just the right amount of bizzare. Might want to check it out.)</p>

<p>Relatedly, I loved the banter from Ryan, the friendly Gleam+Lustre digs between Hayleigh and Jeroen (and Jeroen’s dry, on-the-edge humour all around), John’s all-around energy and witty remarks, Mario’s Evan impressions seconds before Evan comes to the room and Mario is forced to repeat and explain… The mood was electric, everybody is just so smart and friendly, Elm Camp was really something special.</p>

<p>The sessions! Oh the sessions. People pitched in topics they would like to talk about, learn more about, discuss, hack on, get help with, and folks flowed in and out of those sessions as their interest led them. There were some almost keynote-like sessions that basically stole attention from all the other sessions in their timeslot (Evan’s and Mario’s), but that was very well expected.</p>

<p><a href="/assets/images/2024-06-22-notes-from-elm-camp-2024/IMG_20240619_185508914.jpg"><img src="/assets/images/2024-06-22-notes-from-elm-camp-2024/IMG_20240619_185508914.jpg" alt="4" /></a></p>

<p>I’ve held a few sessions myself (elm-bend, elm-syntax-type-inference, stealing sourcemaps from Gren, how to combat Html.Lazy brittleness) and I’m blown away by the amount of ideas from everybody and how in some cases it suddenly became very clear which way to go, what needs to be done next, what’s the best course of action, to improve the Elm ecosystem, level-up the community and so on.</p>

<p>The below is a little list of exactly that - ideas and tasks I don’t want to forget, aimed at nobody in particular, but I would love to take some of these steps and perhaps others would like to as well:</p>

<ul>
  <li>Html.Lazy brittleness
    <ul>
      <li>Make a patch to the compiled elm/virtual-dom JS that checks for Elm equality in the unhappy path where the referential-equality JS <code>===</code> has already failed. If JS <code>===</code> fails and Elm <code>==</code> succeeds, this is a wasteful render and we should either send an error to Sentry or wherever, or console.warn, or blink the element that got re-rendered. (See below)</li>
      <li>Show the viability of Chrome Devtools’ “Paint flashing” for debugging Html.Lazy</li>
      <li>Make a blogpost or a video or Elmcraft digital garden page about how to use and not use Html.Lazy. What breaks it, what is risky, what the compiled JS is for various Elm expressions and declarations, etc. Make the missing piece of docs that should have existed in elm/html. Coordinate with Jeroen, he has a draft blogpost about some of this already.</li>
      <li>Make an experimental patch to elm/virtual-dom that uses the Gren approach: always use Elm <code>==</code> instead of the JS <code>===</code>. This makes the optimization worse, but removes the brittleness and all the confusion in heads of Elm devs who outside of Html.Lazy didn’t need to think about referential equality and how the Elm compiler compiles various Elm snippets.</li>
      <li>Possibly coordinate with Simon Lydell on having some of these patches in elm-watch.</li>
    </ul>
  </li>
  <li>Sourcemaps
    <ul>
      <li>We’ve got a green light from Evan on possibly, after a specific compiler optimization is merged, having sourcemap generation in Elm compiler itself</li>
      <li>Until then, we’ll make a standalone tool (probably, rather than extend Lamdera or elm-dev, due to having to touch the AST definitions and not making a clean diff) for generating sourcemaps for an Elm codebase. This can be done (tediously but without much brain activity needed) by applying the patch that added sourcemaps to Gren to an Elm compiler fork.</li>
    </ul>
  </li>
  <li>elm-test
    <ul>
      <li>Ed Kelly had a need for keeping the equality failure rendering in elm-test while also having a custom failure message. Right now adding a custom failure message removes the equality one. Let’s have both!</li>
    </ul>
  </li>
  <li>elm-bend
    <ul>
      <li>I need to continue on translating various Elm AST nodes to Bend. Right now I’m stuck on case..of expressions (the AST.Optimized node is too tailored to JS and not useful to me. I’ll need the AST.Canonical one.), but there are a bunch of lower hanging fruits for anybody interested in dipping their toes into Elm compiler development.</li>
    </ul>
  </li>
  <li>elm-syntax-type-inference
    <ul>
      <li>Again, case..of is missing, alongside let expressions and then maybe some other stuff. It seems like we’re nearing the finish line!</li>
      <li>But also, from some discussions with Jeroen, Mario and others it seems like maybe elm-review could get the type inference from other sources. The performance of that is unclear so for now we’ll keep chugging on with elm-syntax-type-inference.</li>
    </ul>
  </li>
  <li>elm-grammar
    <ul>
      <li>Probably could be published.</li>
      <li>Mark has given me a cool usecase for these dynamic parsers: developing, testing and iterating on your EBNF grammar in a web interface, regex101-style. Might be worth spinning up a quick application hosted on Github Pages or whatever.</li>
    </ul>
  </li>
  <li>Elm Store pattern
    <ul>
      <li>A cool experiment would be to show how Elm apps could work with data streaming in from the server via WebSockets. (Outside Lamdera. Lamdera makes it trivial.) Right now frontends usually pretend the data they got from the backend is up-to-date even though it isn’t. What would it look like if we had auto-update of the loaded data, or notifying the user about new data available on the server (“this table has new data now, reload by clicking this button”)? What kind of backend do you need? GraphQL subscriptions? Some other WebSocket solution? Would be great to see end-to-end.</li>
      <li>I could have done a better job explaining the Store pattern - better differentiate between the essentials (a record of RemoteData-like API responses, shared between pages) and the rest (Page.Foo.dataDependencies : List DataDependency etc.). Maybe revise the repo or something?</li>
    </ul>
  </li>
</ul>

<p>There’s probably more and I’m already forgetting stuff. Didn’t even mention all the cool stuff that others had in their talks, but I’m also not sure what is sworn by secrecy and what isn’t :) All in all I have a great feeling coming home from Elm Camp: there’s so much cool stuff happening everywhere. The future is bright, and I can’t wait to see it happen.</p>

<p>Thanks to the organizers for making an awesome event and getting the community together. Katja, Mario, Hayleigh, Wolfgang, James, I appreciate you! Thanks to all the Elm people who made it so enjoyable to be there. You’re awesome, everybody.</p>

<p>Oh and here’s to meeting in a year! Maybe in the Czech Republic this time, wink wink?</p>

<p><a href="/assets/images/2024-06-22-notes-from-elm-camp-2024/IMG_20240619_185811942.jpg"><img src="/assets/images/2024-06-22-notes-from-elm-camp-2024/IMG_20240619_185811942.jpg" alt="5" /></a></p>]]></content><author><name>Martin Janiczek</name></author><category term="Posts" /><summary type="html"><![CDATA[]]></summary></entry></feed>