<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss"
        xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
        xmlns:media="http://search.yahoo.com/mrss/"><channel>
<title>Entropic Thoughts</title>
<atom:link href="https://entropicthoughts.com/feed.xml" rel="self" type="application/rss+xml" />
<link>https://entropicthoughts.com/</link>
<description><![CDATA[]]></description>
<language>en</language>
<pubDate>Tue, 20 Oct 2026 12:39:39 +0200</pubDate>
<lastBuildDate>Tue, 20 Oct 2026 12:39:39 +0200</lastBuildDate>
<generator>Emacs 31.1 Org-mode 10.0-pre</generator>
<webMaster>a@xkqr.org (kqr)</webMaster>
<image>
<url>https://entropicthoughts.com/image/favicon/favicon-48x48.png</url>
<title>Entropic Thoughts</title>
<link>https://entropicthoughts.com/</link>
</image>

<item>
<title>NixOS Is Making Me Less Afraid</title>
<link>https://entropicthoughts.com/nixos-is-making-me-less-afraid</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/nixos-is-making-me-less-afraid</guid>
<pubDate>Tue, 20 Oct 2026 00:00:00 +0200</pubDate>
<category><![CDATA[nixos]]></category>
<category><![CDATA[sysadmin]]></category>
<description><![CDATA[<p>
Something funny happened a little while ago: I read about FreshRSS, and it
sounded nice, so I <i>installed it on a server and started using it</i>. That would
not have happened a year ago, but then again, a year ago I didn&rsquo;t have any NixOS
servers.
</p>



<p>
For the past few years, my RSS feeds have lived locally on my phone only.
There was a good idea behind that, but it never quite worked in practice. For one,
many things are easier to read on a larger screen. But it was also brittle to
trust a phone app to manage the read/unread status of thousands of articles
across a hundred feeds. What I really wanted was a server-side feed aggregator. This is software
that runs on a server, manages RSS feeds, and then I can read the RSS feeds
either through a web interface, or through client applications. I&rsquo;ve wanted that
for a while, but I&rsquo;ve never taken the step, because it would be yet another side
project to maintain the deployment of.
</p>



<p>
But now, with some NixOS experience, adding a working FreshRSS configuration
(including backups) took 9 lines of Nix code. That installation has
just hands-off worked for the past few months. If or when that server breaks, I
can spin up a new one running the exact same configuration, probably in
something like 15 minutes. When we can do something like that, there is
literally no reason to not just try the new software.
</p>



<p>
Contrast this against how FreshRSS is installed outside of NixOS. Then we&rsquo;d
</p>


<p><a href="https://entropicthoughts.com/nixos-is-making-me-less-afraid">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>The geometric model of maintenance</title>
<link>https://entropicthoughts.com/geometric-model-of-maintenance</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/geometric-model-of-maintenance</guid>
<pubDate>Tue, 13 Oct 2026 00:00:00 +0200</pubDate>
<category><![CDATA[statistics]]></category>
<category><![CDATA[programming]]></category>
<description><![CDATA[<p>
James Shore posits that each person-month spent writing code results in 10 days
of maintenance in the first year, then 5 days every year after that. Naïvely
interpreted, this is a <i>threshold</i>-based model for induced maintenance, and its
interesting because it&rsquo;s so simple.
</p>



<p>
If it could be shown to be true, that&rsquo;d be useful. The argument Shore is
making about the effects of robot-generated code does not depend on the specific
numbers, so Shore&rsquo;s numbers come from a hypothetical wisdom-of-the-crowds
aggregation rather than concrete data. But no hypotheticals are necessary here.
We have a thing called git.
</p>



<p>
Let&rsquo;s say we have a way of labeling each commit as either &ldquo;new development&rdquo; or
&ldquo;maintenance&rdquo;. Then we can count how many commits of each type there are in a
month. Possibly better, we can count how much
complexity has been handled in a month.
</p>



<p>
The following plot shows the cumulative effort that has gone into the
development of tmux since its beginning to the day I start writing this article.
</p>


<p><a href="https://entropicthoughts.com/geometric-model-of-maintenance">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Swemak: Colemak for Swedish</title>
<link>https://entropicthoughts.com/swemak</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/swemak</guid>
<pubDate>Thu, 08 Oct 2026 00:00:00 +0200</pubDate>
<category><![CDATA[life]]></category>
<description><![CDATA[<p>
I have written before about my Swedish Colemak hack, but I never explained what
makes it so good. This is a better image what it looks like:
</p>




<div id="org06a081f" class="figure">
<p>
</p>
</div>



<p>
If you&rsquo;re struggling to find the difference against normal Colemak, look at the
cluster of five keys next to the return key.
</p>



<p>
This layout makes room for the three Swedish letters <code>Å</code>, <code>Ä</code>, and <code>Ö</code>, by only
changing five keys in total, and they have changed very thoughtfully:
</p>


<p><a href="https://entropicthoughts.com/swemak">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Predicting Peace</title>
<link>https://entropicthoughts.com/forecasting-peace</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/forecasting-peace</guid>
<pubDate>Tue, 06 Oct 2026 00:00:00 +0200</pubDate>
<category><![CDATA[forecasting]]></category>
<category><![CDATA[statistics]]></category>
<description><![CDATA[<p>
I have long struggled with predicting when peace will occur. Actually, I&rsquo;m
struggling with predicting anything relating to war, but let&rsquo;s focus on when peace arrives for now. There are two such
questions in the current ACX 2026 competition:
</p>



<ul class="org-ul">
<li>Will there be a cease-fire in Ukraine in 2026?</li>
<li>Will there be a cease-fire in Sudan in 2026?</li>
</ul>



<p>
When I entered my forecast for these questions, I did it based on Laplace&rsquo;s rule
of succession, i.e. as a function of their duration only. This is sort of
justified if war durations are heavy-tailed, which would mean that the longer
they go on for, the less likely it is that peace comes soon. But I don&rsquo;t
know if that is true.
</p>



<p>
Either way, this first, roughest forecast is
</p>


<p><a href="https://entropicthoughts.com/forecasting-peace">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Donating to open source</title>
<link>https://entropicthoughts.com/open-source-donation</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/open-source-donation</guid>
<pubDate>Tue, 29 Sep 2026 00:00:00 +0200</pubDate>
<category><![CDATA[life]]></category>
<description><![CDATA[<p>
I have decided to start donating a small amount of my fun money to open source
projects. After some research, the initial set of recipients are
</p>



<ul class="org-ul">
<li>Magit</li>
<li>Perl 5 Core Maintenance Fund</li>
<li>XMonad</li>
</ul>



<p>
These are important to me, they need more money, and I believe they can use
donations well. If you disagree, I&rsquo;d genuinely like to hear what mistakes I&rsquo;ve
made in the analysis below. I hope it shows that this is important to me.
</p>



<p>
(Which is also why it is so heart-warming when people reach out to tell me this
article prompted them to donate some of their own money. Thank you for being so
kind.)
</p>


<p><a href="https://entropicthoughts.com/open-source-donation">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Shrinking a Fedora virtual disk</title>
<link>https://entropicthoughts.com/shrinking-fedora-virtual-disk</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/shrinking-fedora-virtual-disk</guid>
<pubDate>Thu, 24 Sep 2026 00:00:00 +0200</pubDate>
<category><![CDATA[sysadmin]]></category>
<description><![CDATA[<p>
I have a weird setup for my laptop. In summary, I have been irrationally scared
of running Fedora on bare, dual-GPU metal, so I run it in a virtual machine
with Hyper-V on Windows as a hypervisor. The idea was to let Windows manage
GPUs and power management stuff like hibernation, and then use Fedora as my main
UI to the computer.
</p>



<p>
This has worked surprisingly well, but that&rsquo;s only because my expectations were
low to begin with. The VM can barely access the hardware of the underlying
machine: it can&rsquo;t use the GPU, it can&rsquo;t use the front-facing camera,
Linux starts thrashing weirdly when the VM runs out of memory, among other quirks I&rsquo;ve learned to work around. Thus, I&rsquo;ve wanted
to re-install on bare metal for a while, but! problem!
</p>



<p>
Come on a journey with me. It&rsquo;s got a hero with hubris, mistakes, tension, and a
cliffhanger.
</p>



<p>
During a transition period, I want to have two copies of the Fedora
installation, and I couldn&rsquo;t figure out how to fit it in:
</p>


<p><a href="https://entropicthoughts.com/shrinking-fedora-virtual-disk">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Cheaper LLM labelling</title>
<link>https://entropicthoughts.com/cheaper-llm-labeling</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/cheaper-llm-labeling</guid>
<pubDate>Tue, 22 Sep 2026 00:00:00 +0200</pubDate>
<category><![CDATA[statistics]]></category>
<category><![CDATA[ai]]></category>
<category><![CDATA[perl]]></category>
<category><![CDATA[programming]]></category>
<description><![CDATA[<p>
I have a small project where I needed to label commits as either &ldquo;maintenance&rdquo;
or &ldquo;new development&rdquo;. The obvious way to do it is with a cheap but relatively
capable LLM, like GPT 5.6 Luna. I tested it on a small set of commits and
manually verified its labelling, and it emitted the same label as I would have
for the entire test set. That was good enough for me to roll out on a wider scale.
</p>



<p>
If we have Simon Willison&rsquo;s <code>llm</code> CLI tool installed (and you should – it&rsquo;s
great!), we can call it in a pipe from Perl, and read its response. My script
had a loop that retried the request a few times, but without that bookkeeping,
the code for this is simple enough.
</p>



<label class="input">In[1]:</label><div class="org-src-container">
<pre class="src src-perl"><code>sub classify {
    my ($msg) = @_;
    my $pid = open2(
        my $output,
        my $prompt,
        'llm -m openrouter/openai/gpt-5.6-luna'
    );
    print $prompt prompt_template($msg);
    close $prompt;

    # Slurp the entire response and chomp off the
    # trailing newline.
    chomp(my $result = do { local $/; &lt;$output&gt; });
    waitpid($pid, 0);

    return $result;
}
</code></pre>
</div>



<p>
The drawback of this approach is that I wanted to label some 21,000 commits, and
this shells out to the LLM for every single commit. Since the prompt is
designed to contain mainly input tokens and not many output tokens (see Appendix
C for details), these calls are cheap in money, but the latency is no fun, at
around 1.5 seconds per call.
</p>


<p><a href="https://entropicthoughts.com/cheaper-llm-labeling">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Tag index for Org mode blog</title>
<link>https://entropicthoughts.com/tag-index-for-org-mode-blog</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/tag-index-for-org-mode-blog</guid>
<pubDate>Tue, 15 Sep 2026 00:00:00 +0200</pubDate>
<category><![CDATA[meta]]></category>
<category><![CDATA[programming]]></category>
<description><![CDATA[<p>
Since people keep asking how this blog is made, and I don&rsquo;t want to share the
awful, terrible code that it is taped together with, I&rsquo;ve decided to start
explaining parts of it piecewise. Generally, any time something breaks and I
have to fix it, I write down what I did and what it connects to.
</p>



<p>
The most recent issue was the stack limit being blown by a helper function
involved in generating the tag index. I had written it to be explicitly
recursive, which worked fine with a small-ish number of published articles, but
not anymore. The tag index creation follows a similar pattern to the RSS feed
generation detailed in the previous article.
</p>



<label class="input">In[1]:</label><div class="org-src-container">
<pre class="src src-elisp"><code>(defun tw-tag-index (project)
  "Generate a tag index page for PROJECT."
  (let* ((filename (concat (project-dir "src" "org") "tags.org"))
         ;; Get all publically listed files in this project.
         (files (seq-filter (lambda (entry) (plist-get entry :indexed))
                            (tw-get-all-files project)))
         ;; Convert to sorted alist of tag×article pairs.
         (all-tags (sort (tw-get-all-file-tags files)
                         (lambda (a b) (string&lt; (car a) (car b))))))
    (with-temp-file filename
      (insert (concat
               "#+TITLE: Tags\n"
               ;; Don't show author and publish date.
               "#+AUTHOR: \n"
               ;; Don't list in public indices.
               "#+FILETAGS: :page:\n\n"
               ;; Include table of contents.
               "#+TOC: headlines:1\n\n"
               ;; Then produce headings for each tag.
               (org-element-interpret-data
                   (tw-article-entry-headlines all-tags)))))
    ;; Return the filename of the created file. I don't
    ;; remember why. Maybe it's not necessary.
    filename))
</code></pre>
</div>



<p>
First we filter out the articles that should be indexed from a list of all files
in the project. We pass this through the <code>tw-get-all-file-tags</code> function, which
produces an <i>association list</i>, which is like a dictionary in Emacs
Lisp. Each entry in the association list is a tag paired with a list of articles
that have that tag. That list is sorted alphabetically on the tag name, and then
it gets converted into an Org document that can be exported.
</p>


<p><a href="https://entropicthoughts.com/tag-index-for-org-mode-blog">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Better AI code comment detector</title>
<link>https://entropicthoughts.com/better-ai-comment-classifier</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/better-ai-comment-classifier</guid>
<pubDate>Wed, 09 Sep 2026 00:00:00 +0200</pubDate>
<category><![CDATA[ai]]></category>
<category><![CDATA[statistics]]></category>
<category><![CDATA[programming]]></category>
<description><![CDATA[<p>
When I trained the previous AI comment classifier, I used partially personal
private data to do it, and built it on a somewhat shaky foundation, so I
couldn&rsquo;t share the code or data. I rebuilt it on public data and a better
foundation!
</p>



<p>
First off, you might want to try it out. Nothing you paste into that web page
leaves your browser, so you can safely try it with whatever you like. I have
invited some testers to try out an earlier version of it, and they had mainly
positive feedback to give.
</p>



<p>
We won&rsquo;t break down robot-isms the way we broke down Claude-isms in the previous
article, because in the UI of the new classifier you can just click any part
of the text being classified to see which features activate on that portion of
the text, and how they contribute to the overall judgement. Here&rsquo;s an example of
the expanded feature activation view.
</p>




<div id="orga66a4c6" class="figure">
<p>
</p>
</div>


<p><a href="https://entropicthoughts.com/better-ai-comment-classifier">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Claude comment detection</title>
<link>https://entropicthoughts.com/ai-comment-classifier</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/ai-comment-classifier</guid>
<pubDate>Tue, 01 Sep 2026 00:00:00 +0200</pubDate>
<category><![CDATA[programming]]></category>
<category><![CDATA[ai]]></category>
<category><![CDATA[statistics]]></category>
<description><![CDATA[<p>
Here&rsquo;s a comment I read in the code I was working on.
</p>



<blockquote>
<p>
Walk the entire collection to locate flagged clusters. Skip it altogether when
the cached, dirty-tracked flag count says there are none (the usual case):
nothing needs marking, so the walk is pure waste. When the count is stale (e.g.
right after an edit) or non-zero, we fall through to the real lookup, so stale
marks never show. Same eventually-consistent signal the &ldquo;Flags&rdquo; badge relies on.
</p>
</blockquote>



<p>
One thing in this comment caught my mind: the small parenthetical that says &ldquo;the
usual case&rdquo;. If that were true, it would be important information! However, this
comment is generated by the LLM model Claude, which has <i>no</i> sense of what
counts as the usual case in this domain. But by generating that comment, it
tricks every future reader (both human and robot) into believing in a property
of the system that does not exist.
</p>



<p>
Since I knew, in this case, that the comment was generated by a robot and that
the property it contained was hallucinated, I could fix the comment. But that&rsquo;s
not always so easy. Here&rsquo;s another comment.
</p>


<p><a href="https://entropicthoughts.com/ai-comment-classifier">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>A curmudgeon tries a language server</title>
<link>https://entropicthoughts.com/curmudgeon-tries-language-server</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/curmudgeon-tries-language-server</guid>
<pubDate>Tue, 25 Aug 2026 00:00:00 +0200</pubDate>
<category><![CDATA[haskell]]></category>
<category><![CDATA[programming]]></category>
<description><![CDATA[<p>
I write code roughly the same way I did ten years ago: I make edits in my text
editor, I switch over to a terminal window and I run a command to compile and
execute the code. I look at the result, then switch back to my editor. If I am
unsure of what happens, I either add tracer prints to the code and restart the
program, or I restart the program in a debugger. If I hit an exception that
crashes my program, I fix the problem and then restart it.
</p>



<p>
I&rsquo;m envious of what Lisp programmers do. Lisp development typically happens by
typing code directly into the REPL to add, remove, and replace parts of the
live, running system.
</p>



<ul class="org-ul">
<li>A Lisp programmer does not need to &ldquo;switch over&rdquo; to something else because
they are already inside their program process.</li>

<li>They never &ldquo;compile and execute&rdquo; the code because the code is already running
and they edit it by hot-swapping code.</li>

<li>They never restart in a debugger because they are already inside their program
process and can inspect anything they want.</li>

<li>They don&rsquo;t have to restart the program on exceptions, because the condition
system allows resuming the crashing code <i>from anywhere in the stack</i> after
the system has been patched with a fix.</li>
</ul>



<p>
A consequence of this way of working is that early in a Lisp project, there may
not even be any <i>source code</i> to speak of. Instead, the evolving definition of
the system exists only in the <i>memory image of the running process</i> and nowhere
else. This is so unlike how we normally do development that people have
expressed trouble understanding what that even means. A comparison that might
help is to think of how people treat relational databases in the beginning of a
project. Often, people don&rsquo;t start out with a version-controlled schema
definition; rather, the evolving schema exists only in the running database.
Sure, at some point when the project matures, the database schema is dumped out
to a file, put under version control, and from that point forward, schema
changes are made through explicit migrations. Similarly, a Common Lisp project
that matures will be dumped from memory image to source code eventually, and
then that source code will go under version control, with patches applied more
carefully to the live system.
</p>


<p><a href="https://entropicthoughts.com/curmudgeon-tries-language-server">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Snakes and Ladders</title>
<link>https://entropicthoughts.com/snakes-and-ladders</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/snakes-and-ladders</guid>
<pubDate>Tue, 18 Aug 2026 00:00:00 +0200</pubDate>
<category><![CDATA[games]]></category>
<category><![CDATA[life]]></category>
<description><![CDATA[<p>
It is common for promotional pamphlets aimed at
children to contain a themed variant of <i>snakes and ladders</i>. This is the &ldquo;game&rdquo;
where the players toss a die, move as many places as the die says, and then some
spots on the board have an event that send the player forward, or back, or give
them an extra toss or whatever.
</p>



<p>
The theme is typically related to whatever the promoter wants to promote, but
styled for children. I get the appeal – for children. They can probably imagine
they&rsquo;re really travelling along the path! In the variant pictured below, there&rsquo;s
forest and water and tunnels and dogs and everything!
</p>




<div id="org9f079d9" class="figure">
<p>
</p>
</div>



<p>
But as a game, it <i>sucks</i>. For everyone. My children hate losing, and these
games have <i>n</i>−1 losers for every <i>n</i> players. There are also literally zero
decisions for the players to make, so I cannot make it through even a few turns
without zoning out.
</p>


<p><a href="https://entropicthoughts.com/snakes-and-ladders">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>A noob learns FFT</title>
<link>https://entropicthoughts.com/fft</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/fft</guid>
<pubDate>Tue, 11 Aug 2026 00:00:00 +0200</pubDate>
<category><![CDATA[maths]]></category>
<description><![CDATA[<p>
I have never done any time–frequency transformations. I understand the general
idea, but I have never worked with the details. Today I had a reason to use it,
but that means learning the basics first.
</p>



<p>
The following is three seconds of a single 5 Hz sine wave, recorded at a sample
rate of 100 Hz.
</p>




<div id="org53af969" class="figure">
<p>
</p>
</div>


<p><a href="https://entropicthoughts.com/fft">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Purely functional digital circuit simulator (SICP 3.3)</title>
<link>https://entropicthoughts.com/sicp-3-3-pure-digital-circuit-simulator</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/sicp-3-3-pure-digital-circuit-simulator</guid>
<pubDate>Wed, 05 Aug 2026 00:00:00 +0200</pubDate>
<category><![CDATA[programming]]></category>
<description><![CDATA[<p>
I have a copy of SICP, or as it is also known, <i>The Wizard
Book</i>. This book is widely praised, but I can&rsquo;t take the
time to work my way through all of it. Instead, I&rsquo;m going to occasionally jump
into the parts of it that look interesting.
</p>



<p>
Since last week, are in the process of simulating a digital circuit. The reason
this is interesting is the solution in SICP uses hidden mutable state and
message-passing to make the code object-oriented. It even uses a mutable global
variable for scheduling! We managed to replicate all of that in Haskell, but now
we want to refactor the solution to be easier to work with.
</p>



<p>
If we are going to manage the simulation in a pure functional manner, we still
have to contend with the fact that during simulation, wires are objects with a
fixed identity. A wire does not become a different wire just because its signal
changes – the same wire is still hooked up to the same gates. Wires need to
maintain their identity somehow.
</p>



<p>
In addition to the <code>Signal</code> type from before, we&rsquo;ll define a <code>Wire</code> to be a
string. This is its identity. We&rsquo;ll also make a convenience function for
creating a wire whose name is based on another wire name. This will be useful
for creating unique wire names when we build circuits.
</p>


<p><a href="https://entropicthoughts.com/sicp-3-3-pure-digital-circuit-simulator">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Digital circuit simulator in Haskell (SICP 3.3)</title>
<link>https://entropicthoughts.com/sicp-3-3-digital-circuit-simulator-in-haskell</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/sicp-3-3-digital-circuit-simulator-in-haskell</guid>
<pubDate>Mon, 27 Jul 2026 00:00:00 +0200</pubDate>
<category><![CDATA[programming]]></category>
<description><![CDATA[<p>
I have a copy of SICP, or as it is also known, <i>The Wizard
Book</i>. This book is widely praised, but I can&rsquo;t take the
time to work my way through all of it. Instead, I&rsquo;m going to occasionally jump
into the parts of it that look interesting.
</p>



<p>
In the previous two installations of this series, we looked at ways of
implementing generic functions. In the first one, by tagging values and
dispatching operations on tags. In the second case, by filling a mutable table
of operation–tag pairs. We saw how these are roughly equivalent to the existing
Haskell features of sum types and type classes.
</p>



<p>
Now we&rsquo;ll simulate a digital circuit. The reason this is interesting is that the
solution in SICP uses hidden mutable state and message-passing to make the
code object-oriented. It even uses a mutable global variable for scheduling! I
wasn&rsquo;t sure if it would be possible to replicate that in Haskell, but as we&rsquo;ll
see, it can get incredibly close.
</p>



<p>
There are many ways to implement mutable state in Haskell, but to begin with,
we&rsquo;ll use the way that&rsquo;s most similar to the SICP solution: <code>IORef</code>s. An
<code>IORef</code> is a mutable variable, like in any other programming language.
</p>


<p><a href="https://entropicthoughts.com/sicp-3-3-digital-circuit-simulator-in-haskell">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Kuiper Q-Q plot: are these the same?</title>
<link>https://entropicthoughts.com/kuiper-q-q-plot</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/kuiper-q-q-plot</guid>
<pubDate>Tue, 21 Jul 2026 00:00:00 +0200</pubDate>
<category><![CDATA[statistics]]></category>
<description><![CDATA[<p>
When we tried to get an intuition for the differences in distribution functions,
we learned that children subjected to a treatment had greater variation in their
score than the reference group.
</p>




<div id="org2794617" class="figure">
<p>
</p>
</div>



<p>
However, we couldn&rsquo;t tell if this difference was large enough to be a useful
guide for making decisions. It could just be an artifact of the small sample
size.
</p>


<p><a href="https://entropicthoughts.com/kuiper-q-q-plot">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Intuition for distribution differences</title>
<link>https://entropicthoughts.com/distribution-differences</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/distribution-differences</guid>
<pubDate>Tue, 14 Jul 2026 00:00:00 +0200</pubDate>
<category><![CDATA[statistics]]></category>
<description><![CDATA[<p>
Imagine every person in a country is assigned some sort of score, and a higher
score is better. I can&rsquo;t share the specifics around the score I&rsquo;m investigating
right now, but on the population level it is collected
by a government agency. Here&rsquo;s the distribution of these scores for the
population in my country.
</p>




<div id="orge60473b" class="figure">
<p>
</p>
</div>



<p>
Since the curve does not start at the origin but a few percent up on the y axis,
we can conclude that a few percent of the population have a score of zero. That
doesn&rsquo;t mean they are bad or miserable people – there may be factors that make
zero the best score for them – but on average, a score of zero is worse than a
higher score.
</p>


<p><a href="https://entropicthoughts.com/distribution-differences">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>A full body MRI earns you a year of smoking</title>
<link>https://entropicthoughts.com/full-body-mri-earns-you-a-base-jump</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/full-body-mri-earns-you-a-base-jump</guid>
<pubDate>Tue, 07 Jul 2026 00:00:00 +0200</pubDate>
<category><![CDATA[statistics]]></category>
<description><![CDATA[<p>
Alternative titles:
</p>



<ul class="org-ul">
<li>… earns you a high-risk pregnancy</li>
<li>… earns you an ascent of Matterhorn</li>
<li>… earns you 10,000 km on a motorcycle</li>
<li>… earns you two BASE jumps</li>
<li>… earns you a day on the frontline in Ukraine</li>
</ul>



<p>
These are all about equivalent to the risk of one year of smoking.
</p>




<p>
I&rsquo;m skeptical of submitting asymptomatic people to medical tests. Almost any
time I look into the evidentiary power of medical tests, I&rsquo;m struck by how much
work is performed by the base rate. The tests only work because we perform them
on people who appear sick, i.e. have a higher probability of actually being sick
in the first place. If we would perform them on seemingly-healthy people we
would get nonsensical results.
</p>


<p><a href="https://entropicthoughts.com/full-body-mri-earns-you-a-base-jump">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Data-directed programming in Haskell (SICP 2.4.3)</title>
<link>https://entropicthoughts.com/sicp-2-4-data-directed-programming-in-haskell</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/sicp-2-4-data-directed-programming-in-haskell</guid>
<pubDate>Tue, 30 Jun 2026 00:00:00 +0200</pubDate>
<category><![CDATA[haskell]]></category>
<description><![CDATA[<p>
I have a copy of SICP, or as it is also known, <i>The Wizard
Book</i>. This book is widely praised, but I can&rsquo;t take the
time to work my way through all of it. Instead, I&rsquo;m going to occasionally jump
into the parts of it that look interesting. Last week, we looked at tagged data
in Haskell. The authors of SICP weren&rsquo;t convinced that&rsquo;s the best approach, so
they move on to data-directed programming. We&rsquo;ll do the same.
</p>




<p>
Complex numbers can be stored in their rectangular form, with a real
and an imaginary part. They can also be stored in polar form, where there&rsquo;s a
magnitude and an angle. Whichever way a complex number is stored, we would like
to be able to query it for all of these four quantities:
</p>



<ul class="org-ul">
<li>The real coordinate in the rectangular form of the complex number.</li>
<li>The imaginary coordinate in the rectangular form.</li>
<li>The magnitude in the polar form.</li>
<li>The angle in the polar form.</li>
</ul>



<p>
Last time we implemented these on top of a <i>tagged</i> value. To return one of the
four coordinates above, a function would introspect the tag to figure out how to
satisfy the caller for the representation involved. This works, but any time we
want to add a new representation, we have to change all four existing
operations.
</p>


<p><a href="https://entropicthoughts.com/sicp-2-4-data-directed-programming-in-haskell">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Tagged data in Haskell (SICP 2.4.2)</title>
<link>https://entropicthoughts.com/sicp-2-4-tagged-data-in-haskell</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/sicp-2-4-tagged-data-in-haskell</guid>
<pubDate>Tue, 23 Jun 2026 00:00:00 +0200</pubDate>
<category><![CDATA[haskell]]></category>
<description><![CDATA[<p>
I have a copy of SICP, or as it is also known, <i>The Wizard
Book</i>. This book is widely praised, but I can&rsquo;t take the
time to work my way through all of it. However, sometimes I jump into parts of
it that look interesting. Today, we&rsquo;ll see how to support multiple
representations of data through tagging.
</p>



<p>
This article is written in Haskell throughout, but at the start it will look a
lot like the Lisp code in SICP. I have intentionally tried to recreate the
SICP solution as closely as possible, including dynamic typing and all. See
the appendix if you&rsquo;re curious how it works.
</p>



<p>
Complex numbers can be stored in their rectangular form, where there&rsquo;s a real
and an imaginary part. They can also be stored in polar form, where there&rsquo;s a
magnitude and an angle. The authors ask us to imagine that two people have been
working on a library for mathematics, but ended up choosing different ways to
store complex numbers. How can they write their code so that they don&rsquo;t have to
agree on one way to store the data?
</p>



<p>
Abelson and Sussman propose a <i>tagged</i> representation, where the data of the
complex numbers are paired with a tag, which indicates to the implementation
what representation is being used. They suggest the following functions to
attach a tag, as well as inspect tagged data.
</p>


<p><a href="https://entropicthoughts.com/sicp-2-4-tagged-data-in-haskell">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>GLM 5.2 playing text adventures</title>
<link>https://entropicthoughts.com/glm-5-2-playing-text-adventures</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/glm-5-2-playing-text-adventures</guid>
<pubDate>Thu, 18 Jun 2026 00:00:00 +0200</pubDate>
<category><![CDATA[ai]]></category>
<description><![CDATA[<p>
I&rsquo;ve heard some buzz around the new GLM 5.2 open-weights model. They say it&rsquo;s
very capable! I won&rsquo;t run a full comparison benchmark, but I have some credits
sloshing around on OpenRouter so I figured I might compare GLM 5.2 to the
similarly-priced Gemini 3 Flash, and see where things land.
</p>



<p>
This uses the same setup as the previous benchmark: each LLM gets a few
attempts at playing the game, with each attempt being limited to a fixed budget
of around $0.15. The LLM doesn&rsquo;t know it, but the harness tracks achievements
for each game, and counts how many the LLM earns in each attempt.
</p>



<p>
Here are the number of attempts for each game in this run.
</p>



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


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

<col  class="org-right" />
</colgroup>
<thead>
<tr>
<th scope="col" class="org-left">Game</th>
<th scope="col" class="org-right">Attempts per model</th>
</tr>
</thead>
<tbody>
<tr>
<td class="org-left">Lost Pig</td>
<td class="org-right">4</td>
</tr>

<tr>
<td class="org-left">Organ Grinder&rsquo;s Monkey</td>
<td class="org-right">2</td>
</tr>

<tr>
<td class="org-left">Not All That Shimmers</td>
<td class="org-right">3</td>
</tr>

<tr>
<td class="org-left">Kill Wizard</td>
<td class="org-right">3</td>
</tr>

<tr>
<td class="org-left">9:05</td>
<td class="org-right">5</td>
</tr>
</tbody>
<tbody>
<tr>
<td class="org-left">Total</td>
<td class="org-right">17</td>
</tr>
</tbody>
<tbody>
<tr>
<td class="org-left">💸</td>
<td class="org-right">$5.1</td>
</tr>
</tbody>
</table>


<p><a href="https://entropicthoughts.com/glm-5-2-playing-text-adventures">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Lean, not backpressure</title>
<link>https://entropicthoughts.com/lean-not-backpressure</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/lean-not-backpressure</guid>
<pubDate>Tue, 16 Jun 2026 00:00:00 +0200</pubDate>
<category><![CDATA[systems]]></category>
<category><![CDATA[management]]></category>
<category><![CDATA[organisations]]></category>
<category><![CDATA[ai]]></category>
<description><![CDATA[<p>
Lucas Costa has written a good article on how to build systems that can handle
code-generating robots. Unfortunately, when calling it <i>backpressure</i>, he used
the wrong metaphor.
</p>



<p>
Backpressure is about signaling to upstream processes that they are running too
fast and need to slow down. The suggestions presented by Costa are mostly about
signaling to the upstream process that it needs to do things <i>differently</i>,
rather than just slow down. This has more to do with ensuring sufficient
<i>quality</i> is sent downstream, rather than <i>quantity</i>.
</p>



<p>
This irked me. As I was reading, I was searching for the right analogy. I kept
coming back to <i>lean manufacturing</i>. The more famous half of the lean philosophy
is waste reduction. The other half is about managing the unstable input of
people. That&rsquo;s what we&rsquo;re interested in here.
</p>



<p>
A common approach to the input of people – especially in lower-skilled jobs – is
to make line workers responsible for everything. We ask them to be
hypervigilant, tell them to never make mistakes, and let them know that if they
don&rsquo;t always perform at their best, they will be chastised … or fired.
</p>


<p><a href="https://entropicthoughts.com/lean-not-backpressure">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>LLMs and almost good code</title>
<link>https://entropicthoughts.com/llms-and-almost-good-code</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/llms-and-almost-good-code</guid>
<pubDate>Tue, 09 Jun 2026 00:00:00 +0200</pubDate>
<category><![CDATA[ai]]></category>
<category><![CDATA[programming]]></category>
<description><![CDATA[<p>
TL;DR: My new prior is that top-of-the-line LLMs working on <i>easy</i> tasks
generate code that is maybe 10 % more complicated than necessary. I also think
we accept this complexity too easily, because it comes from code that is <i>right
here</i>, <i>right now</i>, solving an immediate problem. This may have consequences
for maintenance in the long term.
</p>



<p>
The background to this discovery was that I needed to do some CRUD plumbing in
a work project. It was a simple change that mostly mirrored existing
functionality. This is a perfect fit for LLMs, in my experience, so I used a
frontier model to generate the code for it. The change ended up being a total of
just over 200 lines, mostly additions.
</p>



<p>
The part of the generated code we&rsquo;ll talk about is a 24-line function that
converts an arbitrary (user-supplied) string to a safe HTTP header
value.
</p>



<label class="input">In[1]:</label><div class="org-src-container">
<pre class="src src-haskell"><code>toHeaderValue :: Text -&gt; Text
toHeaderValue raw =
  let
    attrChars = "!#$&amp;+-.^_`|~"
    padHex t = if Text.length t &lt; 2 then "0" &lt;&gt; t else t
    percentEncode c =
      if (isAscii c &amp;&amp; isAlphaNum c) || elem c attrChars then
        Text.singleton c
      else
        Text.concat
          [ "%" &lt;&gt; padHex (Text.toUpper (Text.pack (showHex b "")))
          | b &lt;- ByteString.unpack (encodeUtf8 (Text.singleton c))
          ]
    rfc5987Encode = Text.concatMap percentEncode
    isPrintable c = c &gt;= ' ' &amp;&amp; c /= '\DEL'
    replacePathSeparator c =
      if c == '/' || c == '\\' then
        '_'
      else
        c
    cleaned =
      Text.map replacePathSeparator (Text.filter isPrintable raw)
  in
    rfc5987Encode cleaned
</code></pre>
</div>


<p><a href="https://entropicthoughts.com/llms-and-almost-good-code">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>Is the Monaco Grand Prix decided at qualifying?</title>
<link>https://entropicthoughts.com/is-monaco-decided-at-qualifying</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/is-monaco-decided-at-qualifying</guid>
<pubDate>Tue, 02 Jun 2026 00:00:00 +0200</pubDate>
<category><![CDATA[statistics]]></category>
<description><![CDATA[<p>
A Formula One driver triggered my fact-checkitis. They claimed that
</p>



<blockquote>
<p>
Winning the Monaco Grand Prix in Monte Carlo is determined nine out of ten times
by which position one starts in.
</p>
</blockquote>



<p>
That makes intuitive sense, because the Monte Carlo track is a narrow street
track with few opportunities for overtakes. But … really? Is that an
off-the-cuff remark or an accurate statistical prediction of the race?
</p>




<p>
If we take the statement as a statistical prediction, we have to clarify what it
means, exactly. I&rsquo;m going to assume it means that 90 % of the time, one of the
cars starting in the first row (the first two positions) will win the race. Very
little research is needed to prove that false. In the past 25 years, the Monaco
race has been won by someone in the first row only 80 % of the time. The data do
not support the hypothesis.
</p>


<p><a href="https://entropicthoughts.com/is-monaco-decided-at-qualifying">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
<item>
<title>90 % of the t distribution</title>
<link>https://entropicthoughts.com/ninety-percent-of-the-t-distribution</link>
<author>a@xkqr.org (kqr)</author>
<guid isPermaLink="false">https://entropicthoughts.com/ninety-percent-of-the-t-distribution</guid>
<pubDate>Tue, 26 May 2026 00:00:00 +0200</pubDate>
<category><![CDATA[forecasting]]></category>
<category><![CDATA[statistics]]></category>
<description><![CDATA[<p>
William Sealy Gosset was great. He improved beer at Guinness by using the
statistics that existed at the time. Not happy with that, he invented new
statistics to brew even better beer. The things he invented are used all over
the place now, but Guinness wanted to keep him a secret weapon, so they made him
publish his results under the fake name <i>Student</i>.
</p>



<p>
One thing Gosset realised is that it is wrong to compute 90 % confidence
intervals for the mean by taking the standard deviation of the sample, and
<i>assume a normal distribution</i>, like-a-so:
</p>



<p>
\[\hat{\mu} \pm 1.645 \hat{\sigma}\]
</p>



<p>
When we do this we get too narrow a range, because while we recognise
\(\hat{\mu}\) is just an approximation, we are assuming we know \(\sigma =
\hat{\sigma}\) with certainty!
</p>


<p><a href="https://entropicthoughts.com/ninety-percent-of-the-t-distribution">(Continue reading the full article on the web.)</a></p>
]]></description>
</item>
</channel>
</rss>
