<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:dc="http://purl.org/dc/elements/1.1/">
    <channel>
        <title>stuebinm.eu</title>
        <link>https://stuebinm.eu</link>
        <description><![CDATA[apparently a blog]]></description>
        <atom:link href="https://stuebinm.eu/rss.xml" rel="self"
                   type="application/rss+xml" />
        <lastBuildDate>Sun, 09 Nov 2025 00:00:00 UT</lastBuildDate>
        <item>
    <title>Machines should know which commit is deployed on them</title>
    <link>https://stuebinm.eu/posts/nix-machine-metadata.html</link>
    <description><![CDATA[<div class="toc-box"><div class="toc">

<ul>
<li><a href="#getting-gits-information" id="toc-getting-gits-information">Getting Git’s Information</a>
<ul>
<li><a href="#without-flakes" id="toc-without-flakes">Without flakes</a></li>
<li><a href="#uncommitted-changes" id="toc-uncommitted-changes">Uncommitted changes</a></li>
</ul></li>
<li><a href="#aside-what-is-a-short-rev-anyways" id="toc-aside-what-is-a-short-rev-anyways">Aside: What is a “Short Rev”, anyways?</a></li>
<li><a href="#telling-the-machine-where-its-at" id="toc-telling-the-machine-where-its-at">Telling the machine where it’s at</a></li>
<li><a href="#monitoring" id="toc-monitoring">Monitoring</a>
<ul>
<li><a href="#monit" id="toc-monit">monit</a></li>
<li><a href="#do-we-know-what-the-currently-deployed-state-is" id="toc-do-we-know-what-the-currently-deployed-state-is">Do we know what the currently deployed state is?</a></li>
<li><a href="#did-we-forget-to-update-the-host" id="toc-did-we-forget-to-update-the-host">Did we forget to update the host?</a></li>
</ul></li>
<li><a href="#alternative-include-the-full-config" id="toc-alternative-include-the-full-config">Alternative: include the full config</a></li>
<li><a href="#conclusion" id="toc-conclusion">Conclusion</a></li>
</ul>
</div></div>
<p>TL;DR: I find it useful to have all the (NixOS) machines I administrate know meta
information about their currently deployed configuration, which allows monitoring
to shout at me if I forget to update one
or (worse) have deployed changes I forgot to commit.</p>
<p>This comes up in conversation every now and then, and surprisingly often people
are surprised how easily this can be done. Hence I thought I’d write a summary
of what I do as a little reference I can point people to in the future.</p>
<h2 id="getting-gits-information">Getting Git’s Information</h2>
<p>This is fairly easy, whether using the (still experimental!) flakes or not.
Flakes, by design, have a tighter integration to the underlying git repository
(assuming the directory of the flake <em>is</em> a git repository, which it does not have
to be) then we get information about it as part of the flake’s <code class="sourceCode nix">self</code> input:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">inputs</span> <span class="op">=</span> <span class="op">{</span> <span class="op">...</span> <span class="op">};</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>  </span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>  <span class="va">outputs</span> <span class="op">=</span> <span class="op">{</span> <span class="va">self</span> <span class="op">}</span>: <span class="op">{</span> <span class="kw">inherit</span> self<span class="op">;</span> <span class="op">};</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>Merely evaluating the <code>self</code> output here gives something like</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="op">{</span> </span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">_type</span> <span class="op">=</span> <span class="st">&quot;flake&quot;</span><span class="op">;</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>  <span class="va">inputs</span> <span class="op">=</span> <span class="op">{</span> <span class="op">};</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>  <span class="va">lastModified</span> <span class="op">=</span> <span class="dv">1761994565</span><span class="op">;</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>  <span class="va">lastModifiedDate</span> <span class="op">=</span> <span class="st">&quot;20251101105605&quot;</span><span class="op">;</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>  <span class="va">narHash</span> <span class="op">=</span> <span class="st">&quot;sha256-5USBhx5RlZ6YVQiukrj5rsBXDZwO3d1QbNkLoixEFgc=&quot;</span><span class="op">;</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>  <span class="va">outPath</span> <span class="op">=</span> <span class="st">&quot;/nix/store/z66skpk7izzap6336lsd091wpmcpk1v2-source&quot;</span><span class="op">;</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>  <span class="va">outputs</span> <span class="op">=</span> <span class="op">{</span> <span class="va">self</span> <span class="op">=</span> «repeated»<span class="op">;</span> <span class="op">};</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>  <span class="va">rev</span> <span class="op">=</span> <span class="st">&quot;972aa7dd2f313733f551475115c6bb2cc1abdb57&quot;</span><span class="op">;</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>  <span class="va">revCount</span> <span class="op">=</span> <span class="dv">2</span><span class="op">;</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>  <span class="va">self</span> <span class="op">=</span> «repeated»<span class="op">;</span></span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a>  <span class="va">shortRev</span> <span class="op">=</span> <span class="st">&quot;972aa7d&quot;</span><span class="op">;</span></span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a>  <span class="va">sourceInfo</span> <span class="op">=</span> <span class="op">{</span> </span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a>    <span class="va">lastModified</span> <span class="op">=</span> <span class="dv">1761994565</span><span class="op">;</span></span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a>    <span class="va">lastModifiedDate</span> <span class="op">=</span> <span class="st">&quot;20251101105605&quot;</span><span class="op">;</span></span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a>    <span class="va">narHash</span> <span class="op">=</span> <span class="st">&quot;sha256-5USBhx5RlZ6YVQiukrj5rsBXDZwO3d1QbNkLoixEFgc=&quot;</span><span class="op">;</span></span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a>    <span class="va">outPath</span> <span class="op">=</span> <span class="st">&quot;/nix/store/z66skpk7izzap6336lsd091wpmcpk1v2-source&quot;</span><span class="op">;</span></span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a>    <span class="va">rev</span> <span class="op">=</span> <span class="st">&quot;972aa7dd2f313733f551475115c6bb2cc1abdb57&quot;</span><span class="op">;</span></span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a>    <span class="va">revCount</span> <span class="op">=</span> <span class="dv">2</span><span class="op">;</span></span>
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true" tabindex="-1"></a>    <span class="va">shortRev</span> <span class="op">=</span> <span class="st">&quot;972aa7d&quot;</span><span class="op">;</span></span>
<span id="cb2-21"><a href="#cb2-21" aria-hidden="true" tabindex="-1"></a>    <span class="va">submodules</span> <span class="op">=</span> <span class="cn">false</span><span class="op">;</span></span>
<span id="cb2-22"><a href="#cb2-22" aria-hidden="true" tabindex="-1"></a>  <span class="op">};</span></span>
<span id="cb2-23"><a href="#cb2-23" aria-hidden="true" tabindex="-1"></a>  <span class="va">submodules</span> <span class="op">=</span> <span class="cn">false</span><span class="op">;</span></span>
<span id="cb2-24"><a href="#cb2-24" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>Entertainingly (as part of what appears to be a fundamental law of all things nix)
we even get the same information twice.</p>
<h3 id="without-flakes">Without flakes</h3>
<p>What do we do if <a href="https://jade.fyi/blog/pinning-nixos-with-npins/">we don’t use flakes</a>?
In that case, the decision to keep our source files in a git repository is decoupled
from the fact that they’re a human-appreciable collection of source files: nix
does not know or care that they’re in a git repository, and so won’t give us the
information unless we ask for it explicitly.</p>
<p>How do we ask? By simply misusing <code class="sourceCode nix"><span class="bu">builtins</span>.fetchGit</code>:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="bu">builtins</span>.fetchGit <span class="ss">./.</span></span></code></pre></div>
<p>This will, if it’s evaluated in the root file of the git repository, clone it &amp; give
use essentially the same information that we saw before:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">lastModified</span> <span class="op">=</span> <span class="dv">1761994565</span><span class="op">;</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>  <span class="va">lastModifiedDate</span> <span class="op">=</span> <span class="st">&quot;20251101105605&quot;</span><span class="op">;</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>  <span class="va">narHash</span> <span class="op">=</span> <span class="st">&quot;sha256-5USBhx5RlZ6YVQiukrj5rsBXDZwO3d1QbNkLoixEFgc=&quot;</span><span class="op">;</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>  <span class="va">outPath</span> <span class="op">=</span> <span class="st">&quot;/nix/store/z66skpk7izzap6336lsd091wpmcpk1v2-source&quot;</span><span class="op">;</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>  <span class="va">rev</span> <span class="op">=</span> <span class="st">&quot;972aa7dd2f313733f551475115c6bb2cc1abdb57&quot;</span><span class="op">;</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>  <span class="va">revCount</span> <span class="op">=</span> <span class="dv">2</span><span class="op">;</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>  <span class="va">shortRev</span> <span class="op">=</span> <span class="st">&quot;972aa7d&quot;</span><span class="op">;</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>  <span class="va">submodules</span> <span class="op">=</span> <span class="cn">false</span><span class="op">;</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>(Though disappointingly, this time, we only get it once)</p>
<div class="side-note">
<p><span class="header">What does fetchGit do?</span></p>
<p>For those whose entry to using nix was with flakes &amp; who are used to nix always
copying their source files into the store before it does anything else, it may be
worth interrogating for a moment what’s going on here: did we just copy the source
tree <em>twice</em>? No!</p>
<p>In fact the <code>./.</code> path here will happily desugar to the <em>actual path</em> of the file
in which it is contained, even if that happens to be outside the nix store. Unless
the pure evaluation mode is enabled (e.g. via <code>--pure</code> or by using flakes, where
it is the default), this is allowed. Then <code>fetchGit</code> will clone it, doing no more
work than the copying of a flake into the store in the other case.</p>
<p>Note that this means this approach does <em>not</em> work from inside a flake!</p>
</div>
<h3 id="uncommitted-changes">Uncommitted changes</h3>
<p>When evaluating a flake in a “dirty” worktree which contains uncommitted changes,
nix will produce a warning, and produce a slightly different attribute set:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="op">{</span> </span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">_type</span> <span class="op">=</span> <span class="st">&quot;flake&quot;</span><span class="op">;</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>  <span class="va">dirtyRev</span> <span class="op">=</span> <span class="st">&quot;9952748d841d18c5434919f457b0cbac4ba53ba7-dirty&quot;</span><span class="op">;</span> </span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>  <span class="va">dirtyShortRev</span> <span class="op">=</span> <span class="st">&quot;9952748-dirty&quot;</span><span class="op">;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>  <span class="va">inputs</span> <span class="op">=</span> <span class="op">{</span> <span class="op">};</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>  <span class="va">lastModified</span> <span class="op">=</span> <span class="dv">1761951614</span><span class="op">;</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>  <span class="va">lastModifiedDate</span> <span class="op">=</span> <span class="st">&quot;20251031230014&quot;</span><span class="op">;</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>  <span class="va">narHash</span> <span class="op">=</span> <span class="st">&quot;sha256-5USBhx5RlZ6YVQiukrj5rsBXDZwO3d1QbNkLoixEFgc=&quot;</span><span class="op">;</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>  <span class="va">outPath</span> <span class="op">=</span> <span class="st">&quot;/nix/store/z66skpk7izzap6336lsd091wpmcpk1v2-source&quot;</span><span class="op">;</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>  <span class="va">outputs</span> <span class="op">=</span> <span class="op">{</span> <span class="va">self</span> <span class="op">=</span> «repeated»<span class="op">;</span> <span class="op">};</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a>  <span class="va">self</span> <span class="op">=</span> «repeated»<span class="op">;</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>  <span class="va">sourceInfo</span> <span class="op">=</span> <span class="op">{</span> </span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a>    <span class="va">dirtyRev</span> <span class="op">=</span> <span class="st">&quot;9952748d841d18c5434919f457b0cbac4ba53ba7-dirty&quot;</span><span class="op">;</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a>    <span class="va">dirtyShortRev</span> <span class="op">=</span> <span class="st">&quot;9952748-dirty&quot;</span><span class="op">;</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a>    <span class="va">lastModified</span> <span class="op">=</span> <span class="dv">1761951614</span><span class="op">;</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a>    <span class="va">lastModifiedDate</span> <span class="op">=</span> <span class="st">&quot;20251031230014&quot;</span><span class="op">;</span></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a>    <span class="va">narHash</span> <span class="op">=</span> <span class="st">&quot;sha256-5USBhx5RlZ6YVQiukrj5rsBXDZwO3d1QbNkLoixEFgc=&quot;</span><span class="op">;</span></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a>    <span class="va">outPath</span> <span class="op">=</span> <span class="st">&quot;/nix/store/z66skpk7izzap6336lsd091wpmcpk1v2-source&quot;</span><span class="op">;</span></span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a>    <span class="va">submodules</span> <span class="op">=</span> <span class="cn">false</span><span class="op">;</span> <span class="op">};</span></span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a>  <span class="va">submodules</span> <span class="op">=</span> <span class="cn">false</span><span class="op">;</span></span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p><code>rev</code> and <code>shortRev</code> have been replaced by <code>dirtyRev</code> and <code>dirtyShortRev</code>.</p>
<p>The same is (almost) true for <code>fetchGit</code>:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">dirtyRev</span> <span class="op">=</span> <span class="st">&quot;9952748d841d18c5434919f457b0cbac4ba53ba7-dirty&quot;</span><span class="op">;</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>  <span class="va">dirtyShortRev</span> <span class="op">=</span> <span class="st">&quot;9952748-dirty&quot;</span><span class="op">;</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>  <span class="va">lastModified</span> <span class="op">=</span> <span class="dv">1761951614</span><span class="op">;</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a>  <span class="va">lastModifiedDate</span> <span class="op">=</span> <span class="st">&quot;20251031230014&quot;</span><span class="op">;</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a>  <span class="va">narHash</span> <span class="op">=</span> <span class="st">&quot;sha256-5USBhx5RlZ6YVQiukrj5rsBXDZwO3d1QbNkLoixEFgc=&quot;</span><span class="op">;</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a>  <span class="va">outPath</span> <span class="op">=</span> <span class="st">&quot;/nix/store/z66skpk7izzap6336lsd091wpmcpk1v2-source&quot;</span><span class="op">;</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a>  <span class="va">rev</span> <span class="op">=</span> <span class="st">&quot;0000000000000000000000000000000000000000&quot;</span><span class="op">;</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a>  <span class="va">revCount</span> <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a>  <span class="va">shortRev</span> <span class="op">=</span> <span class="st">&quot;0000000&quot;</span><span class="op">;</span></span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a>  <span class="va">submodules</span> <span class="op">=</span> <span class="cn">false</span><span class="op">;</span></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p><em>Very annoyingly</em>, the returned attributes differ: unlike with flakes,
the attributes <code>rev</code> and <code>shortRev</code> still exist, but now always give a meaningless
dummy value! On its own this is not a large problem—all the information is still
there—but it’s good to be mindful of this when writing code which consumes this
information and is used both with and without flakes, as otherwise you’ll get
surprising bugs.</p>
<h2 id="aside-what-is-a-short-rev-anyways">Aside: What is a “Short Rev”, anyways?</h2>
<p>Depending on how much experience you have with git, and especially with very large
git repositories (say, Nixpkgs), at this point there might be a question burning
inside you:
What <em>is</em> a “short hash” here, anyways, and exactly how long is it?</p>
<p>Well, if you’re using <a href="https://github.com/NixOS/nix">CppNix</a>, the answer is “it’s exactly
the first seven characters of the full-length commit hash”.</p>
<p>Except this is not at all how git behaves when it shortens commit hashes! If you ask it
for one (e.g. via <code>git rev-parse --short</code>) it will create a “short” prefix of the
full commit hash, but ensure that it is still long enough to <em>uniquely identifies</em>
its referent object in the current
repository, with seven characters being a minimum that might well grow if the
repository is very large.</p>
<div class="side-note">
<p><span class="header">Is this a real issue?</span></p>
<p>Yes! In fact, Nixpkgs itself is a good example, leading to <a href="https://github.com/NixOS/nix/issues/3442">unfixable issues in Hydra</a>:
to find one, simply set git’s <code>core.abbrev</code> value to something
low (say, 7) so it won’t add more characters than strictly necessary, and
then do <code>git log --oneline</code>.</p>
<p>You won’t need much scrolling to find a short hash
that is (at least) 8 characters long, giving you a case where CppNix’s seven character
prefix is ambiguous.</p>
</div>
<p>Why did I specify “CppNix” above? Surely the Nix implementation you choose
should not matter? Isn’t this ecosystem all about reproducability?
Oh, but what a nice world that would be.</p>
<p>Instead, if you use <a href="https://lix.systems">Lix</a>, you will get a <code>dirtyShortRev</code>
that is produced by simply calling out to git — it will always be a unique identifier,
exactly as git would like it to be,
but its precise value and length might well depend on your git config.</p>
<p>Can you already hear the ugly beast rearing its head? Lix will <em>not</em> give you a
guaranteed-unique identifier for the <code>shortRev</code> attribute in the non-dirty case, because
<a href="https://git.lix.systems/lix-project/lix/issues/814">that would break reproducability of flakes</a>:
the length of a guaranteed-unique short hash depends not just on the locked revision
of a git repository input, but also on your git’s config, and worse, on exactly how
much other stuff there is in that git
repository; if it grows, eventually the short revs will grow, too.</p>
<p>As this is not only unlikely to be fixed, but indeed appears conceptionally
impossible to fix at all, it’s probably best to never use the values of <code>shortRev</code>
and <code>dirtyShortRev</code> at all, and prefer the full versions in all cases.</p>
<h2 id="telling-the-machine-where-its-at">Telling the machine where it’s at</h2>
<p>First, the standard standard way to include information like this into a system is to put
it into <code>/etc/os-release</code>, where it can be picked up by various tools - for example,
the boot menu will show it when choosing a generation. Such attributes are
easily set by using the <a href="https://nixos.org/manual/nixos/stable/options#opt-system.nixos.label">system.nixos.label</a>
NixOS options.</p>
<p>Second, I want my machines to immediately tell my what state is on them when
I log in. NixOS and scriptable motd messages are tricky, so instead I set it
from inside Nix:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">formatDate</span> <span class="op">=</span> <span class="va">date</span><span class="op">:</span> <span class="kw">with</span> lib.strings<span class="op">;</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a>      <span class="va">year</span> <span class="op">=</span> substring <span class="dv">0</span> <span class="dv">4</span> date<span class="op">;</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a>      <span class="va">month</span> <span class="op">=</span> substring <span class="dv">4</span> <span class="dv">2</span> date<span class="op">;</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a>      <span class="va">day</span> <span class="op">=</span> substring <span class="dv">6</span> <span class="dv">2</span> date<span class="op">;</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a>      <span class="va">hour</span> <span class="op">=</span> substring <span class="dv">8</span> <span class="dv">2</span> date<span class="op">;</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a>      <span class="va">minute</span> <span class="op">=</span> substring <span class="dv">10</span> <span class="dv">2</span> date<span class="op">;</span></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a>      <span class="va">second</span> <span class="op">=</span> substring <span class="dv">12</span> <span class="dv">2</span> date<span class="op">;</span></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a>    <span class="kw">in</span></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a>      <span class="st">&quot;</span><span class="sc">${</span>year<span class="sc">}</span><span class="st">-</span><span class="sc">${</span>month<span class="sc">}</span><span class="st">-</span><span class="sc">${</span>day<span class="sc">}</span><span class="st"> </span><span class="sc">${</span>hour<span class="sc">}</span><span class="st">:</span><span class="sc">${</span>minute<span class="sc">}</span><span class="st">:</span><span class="sc">${</span>second<span class="sc">}</span><span class="st"> UTC&quot;</span><span class="op">;</span></span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span></span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a>  <span class="va">users</span>.<span class="va">motd</span> <span class="op">=</span> <span class="st">&#39;&#39;</span></span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a><span class="st">    Welcome to </span><span class="sc">${</span>config.networking.hostName<span class="sc">}</span><span class="st">, running NixOS </span><span class="sc">${</span>config.system.nixos.release<span class="sc">}</span><span class="st">!</span></span>
<span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a><span class="st">    Built from </span><span class="sc">${</span>self.dirtyRev <span class="kw">or</span> self.rev<span class="sc">}</span><span class="st">.</span></span>
<span id="cb7-17"><a href="#cb7-17" aria-hidden="true" tabindex="-1"></a><span class="st">    Last commit was at </span><span class="sc">${</span>formatDate self.lastModifiedDate<span class="sc">}</span><span class="st">.</span></span>
<span id="cb7-18"><a href="#cb7-18" aria-hidden="true" tabindex="-1"></a><span class="st">    </span><span class="sc">${</span><span class="kw">if</span> self <span class="op">?</span> dirtyRev <span class="kw">then</span> <span class="st">&quot;</span><span class="sc">\n</span><span class="st">Please remember to commit your changes!</span><span class="sc">\n</span><span class="st">&quot;</span> <span class="kw">else</span> <span class="st">&quot;&quot;</span><span class="sc">}</span></span>
<span id="cb7-19"><a href="#cb7-19" aria-hidden="true" tabindex="-1"></a><span class="st">  &#39;&#39;</span><span class="op">;</span></span>
<span id="cb7-20"><a href="#cb7-20" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>(why did CppNix’s designers think it appropriate to provide a “readable” date format
as a string, but make contain only digits and nothing else, forcing us to re-parse
that inside Nix if we want to have anything readable? I have no clue)</p>
<h2 id="monitoring">Monitoring</h2>
<p>Finally I like to do one more thing as well: Set up some kind of
monitoring which will tell me if I forgot to commit changes I have already deployed.
This is especially important when a machine is administrated by more than one person:
If one of us forgets to commit anything, others are either blocked from working
on the machine, or may unwittingly roll back previous changes.</p>
<p>First, create two files inside <code>/etc</code>:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">environment</span>.<span class="va">etc</span>.<span class="va">commit</span>.<span class="va">text</span> <span class="op">=</span> self.dirtyRev <span class="kw">or</span> self.rev<span class="op">;</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a>  <span class="va">environment</span>.<span class="va">etc</span>.<span class="va">timestamp</span>.<span class="va">text</span> <span class="op">=</span> <span class="bu">builtins</span>.<span class="bu">toString</span> self.lastModified<span class="op">;</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>Of course we could also simply parse this information out of <code>/etc/os-release</code> — but
doing it this way makes it a little easier to write custom scripts, so, eh, why not.</p>
<h3 id="monit">monit</h3>
<p>For reasons incomprehensible to most of my acquaintances, I still use <a href="https://mmonit.com/monit/">monit</a>
for a lot of my monitoring. But it makes it easy to set up little checks that
should cause notifications if they fail, and it’s trivial to set up, especially
compared to more modern cloud-infra monitoring tools I could use on
my usually comparatively tiny setups.</p>
<p>(on the other hand, it comes from the pre-systemd ages when it was seen as reasonable to
write your monitoring daemons in C and run them as root so they could restart arbitrary startup scripts — your
mileage may vary on that in today’s world)</p>
<h3 id="do-we-know-what-the-currently-deployed-state-is">Do we know what the currently deployed state is?</h3>
<p>There is one main check:</p>
<blockquote>
<p>Is the currently deployed commit the one that’s actually the head of the main
branch, as pushed to some forge?</p>
</blockquote>
<p>Most git forges offer convenient APIs for retrieving commit hashes of branch
heads, so this is easily implemented as a little script:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">checkHash</span> <span class="op">=</span> pkgs.writeScriptBin <span class="st">&quot;check-commit-hash&quot;</span> <span class="st">&#39;&#39;</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="st">    #!</span><span class="sc">${</span>lib.getExe pkgs.fish<span class="sc">}</span></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="st">    set wanted (</span><span class="sc">${</span>lib.getExe pkgs.curl<span class="sc">}</span><span class="st"> -s \</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="st">                https://git.example.org/api/v1/repos/config/branches/main \</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a><span class="st">                -H &#39;accept: application/json&#39; | jq -r .commit.id)</span></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a><span class="st">    if test $status != 0</span></span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a><span class="st">      echo &quot;could not reach git.infra4future.de&quot;</span></span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a><span class="st">      exit 2</span></span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a><span class="st">    end</span></span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a><span class="st">    set actual (cat /etc/commit)</span></span>
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a><span class="st">    if test $actual != $wanted</span></span>
<span id="cb9-15"><a href="#cb9-15" aria-hidden="true" tabindex="-1"></a><span class="st">      echo &quot;</span><span class="sc">${</span>config.networking.hostName<span class="sc">}</span><span class="st"> was built on $actual, but commit on main is $wanted&quot;</span></span>
<span id="cb9-16"><a href="#cb9-16" aria-hidden="true" tabindex="-1"></a><span class="st">      exit 1</span></span>
<span id="cb9-17"><a href="#cb9-17" aria-hidden="true" tabindex="-1"></a><span class="st">    end</span></span>
<span id="cb9-18"><a href="#cb9-18" aria-hidden="true" tabindex="-1"></a><span class="st">  &#39;&#39;</span><span class="op">;</span></span>
<span id="cb9-19"><a href="#cb9-19" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span> <span class="op">{</span> </span>
<span id="cb9-20"><a href="#cb9-20" aria-hidden="true" tabindex="-1"></a>  <span class="va">services</span>.<span class="va">monit</span>.<span class="va">config</span> <span class="op">=</span> <span class="st">&#39;&#39;</span></span>
<span id="cb9-21"><a href="#cb9-21" aria-hidden="true" tabindex="-1"></a><span class="st">      check program deployed-commit-on-main path </span><span class="sc">${</span>lib.getExe checkHash<span class="sc">}</span></span>
<span id="cb9-22"><a href="#cb9-22" aria-hidden="true" tabindex="-1"></a><span class="st">            if status == 1 for 64 cycles then alert</span></span>
<span id="cb9-23"><a href="#cb9-23" aria-hidden="true" tabindex="-1"></a><span class="st">            if status == 2 for 3 cycles then alert</span></span>
<span id="cb9-24"><a href="#cb9-24" aria-hidden="true" tabindex="-1"></a><span class="st">  &#39;&#39;</span><span class="op">;</span></span>
<span id="cb9-25"><a href="#cb9-25" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>Conveniently, this has the by-product of also checking for dirty-ness of the
currently deployed commit, as the <code>-dirty</code> at the end of <code>dirtyCommit</code> will
cause the check to fail even if the hashes match.</p>
<h3 id="did-we-forget-to-update-the-host">Did we forget to update the host?</h3>
<div class="sourceCode" id="cb10"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">checkDeployAge</span> <span class="op">=</span> pkgs.writeScriptBin <span class="st">&quot;check-deploy-age&quot;</span> <span class="st">&#39;&#39;</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="st">    #!</span><span class="sc">${</span>lib.getExe pkgs.fish<span class="sc">}</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="st">    set date (date +%s)</span></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a><span class="st">    # we do this indirection here so monit&#39;s config won&#39;t change on each deploy</span></span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a><span class="st">    set deploytimestamp (cat /etc/haccfiles-timestamp)</span></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a><span class="st">    set age (expr $date - $deploytimestamp)</span></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a><span class="st">    if test $age -ge (expr 3600 \* 24 \* 10)</span></span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a><span class="st">      echo &quot;</span><span class="sc">${</span>config.networking.hostName<span class="sc">}</span><span class="st"> has not been deployed since 10 days, perhaps someone should do updates?&quot;</span></span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a><span class="st">      exit 1</span></span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true" tabindex="-1"></a><span class="st">    end</span></span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true" tabindex="-1"></a><span class="st">  &#39;&#39;</span><span class="op">;</span></span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span> <span class="op">{</span></span>
<span id="cb10-16"><a href="#cb10-16" aria-hidden="true" tabindex="-1"></a>  <span class="va">services</span>.<span class="va">monit</span>.<span class="va">config</span> <span class="op">=</span> <span class="st">&#39;&#39;</span></span>
<span id="cb10-17"><a href="#cb10-17" aria-hidden="true" tabindex="-1"></a><span class="st">      check program check-deploy-age path </span><span class="sc">${</span>lib.getExe checkDeployAge<span class="sc">}</span></span>
<span id="cb10-18"><a href="#cb10-18" aria-hidden="true" tabindex="-1"></a><span class="st">            if status == 1 then alert</span></span>
<span id="cb10-19"><a href="#cb10-19" aria-hidden="true" tabindex="-1"></a><span class="st">  &#39;&#39;</span><span class="op">;</span></span>
<span id="cb10-20"><a href="#cb10-20" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<h2 id="alternative-include-the-full-config">Alternative: include the full config</h2>
<p>There’s an alternative to this approach which achieves all the same goals (and
then some), but which I found a little more annoying in practice. Simply do:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">environment</span>.<span class="va">etc</span>.<span class="va">nixfiles</span>.<span class="va">target</span> <span class="op">=</span> self.outPath<span class="op">;</span></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>and the entire configuration that has currently been deployed will always be
immediately available, no matter what has been messed up or forgotten. We actually
ran this on the <a href="https://infra4future.de">infra4future.de</a> server for almost a
year, and it worked well enough.</p>
<p>So why all this extra effort, if it’s this easy? Mainly, it makes deploys horribly
sluggish — there is no good way to cache half a derivation, so <em>each and every deploy</em> (unless it is a complete no-op)
will now require re-uploading the entire configuration to the server, and deploy
speed is effectively capped at however long that takes on whatever uplink one’s
currently using. And even if it’s only a couple kilobytes this can take a while,
especially during debugging sessions from a moving train.</p>
<h2 id="conclusion">Conclusion</h2>
<p>That’s it! Overall, I’m pretty satisfied with this approach &amp; it’s been useful
more than once the last few years.</p>
<p>There is <em>one</em> thing I wish I could add, tho: adding the whole configuration
to the system closure is too costly, but adding a <em>diff</em> to the last commit
(especially in dirty states) would be extremely useful, but I’ve never looked
into how possible that would be … perhaps one day.</p>
]]></description>
    <pubDate>Sun, 09 Nov 2025 00:00:00 UT</pubDate>
    <guid>https://stuebinm.eu/posts/nix-machine-metadata.html</guid>
    <dc:creator>terru</dc:creator>
</item>
<item>
    <title>Git Annex &amp; the reMarkable 2 tablet</title>
    <link>https://stuebinm.eu/posts/git-annex-xochitl.html</link>
    <description><![CDATA[<div class="toc-box"><div class="toc">

<ul>
<li><a href="#too-many-pdfs" id="toc-too-many-pdfs">Too many pdfs …</a></li>
<li><a href="#reading" id="toc-reading">Reading</a>
<ul>
<li><a href="#special-remotes" id="toc-special-remotes">Special remotes</a></li>
<li><a href="#xochitl-ipan-quixichihua-in-amatl" id="toc-xochitl-ipan-quixichihua-in-amatl">Xochitl ipan quixichihua in amatl</a></li>
<li><a href="#git-annex-export-git-annex-wanted" id="toc-git-annex-export-git-annex-wanted">git annex export &amp; git annex wanted</a></li>
</ul></li>
<li><a href="#all-done" id="toc-all-done">All done?</a></li>
</ul>
</div></div>
<p>TL;DR: if you use git-annex and have a reMarkable 2 tablet, you might find
<a href="https://stuebinm.eu/git/git-annex-remote-remarkable2/">this special remote</a> useful.</p>
<h2 id="too-many-pdfs">Too many pdfs …</h2>
<p>It is a truth universally acknowledged (among some people, at least),
that pdfs have an unfortunate tendency to pile
up, forming unstructured heaps in the “Downloads” directory where nothing
that has once been read can ever be found again. Inevitably, things become much
worse once one attempts to sort a few of them into their own directory, renames
files, moves computers …</p>
<p>For a couple years now, <a href="https://git-annex.branchable.com/">git-annex</a> has been
my way to tackle this issue: I have a single git repository which contains
everything I read (and a lot more I don’t); <code>git-annex</code> dutifully takes care of
tracking these files, of moving
the entire thing to new computers, of checking I have copies on other systems before
deleting any local copies, etc — all I have to do is remember to check files into git.</p>
<div class="side-note">
<p><span class="header">git annex assistant</span></p>
<p>In fact, <code>git annex</code> comes with an “assistant” which runs as a daemon so it can
check in new files automatically and even sync them to other devices, but I still
like doing this by hand, and leave notes for myself in the commit messages.</p>
</div>
<p>In a wild twist, the thing designed to be a good archival tool turns out to be
good at archival work.</p>
<h2 id="reading">Reading</h2>
<p>I read a lot, but LCD screens strain my eyes. This is a general problem,
but for reading there’s off-the-shelf solutions:</p>
<figure>
<img src="/images/remarkable.jpg" alt="A reMarkable 2 tablet, sleeping." />
<figcaption aria-hidden="true">A reMarkable 2 tablet, sleeping.</figcaption>
</figure>
<p>Conveniently, this runs a reasonably ‘standard’ Linux — including familiar
busybox and systemd — and it also runs a pre-configured ssh daemon out of the box.</p>
<p>Happily, while the UI software is proprietary, I am also not beholden to the
company’s commercial cloud service to sync files to the device — someone has
<a href="https://ddvk.github.io/rmfakecloud/">already re-implemented that</a> (only one project
<a href="https://github.com/reHackable/awesome-reMarkable">among many</a>; people have also
written a <a href="https://toltec-dev.org/">package manager</a> and even <a href="https://oxide.eeems.codes/">entirely new UIs</a>).</p>
<p>Except – I don’t want any cloud service, be it self-hosted or not! I don’t even
connect the thing to the internet very often — surely, if I can have an ssh session
via its USB port, that should be enough?</p>
<h3 id="special-remotes">Special remotes</h3>
<p><code>git annex</code> has an obvious way to handle such things:
<a href="https://git-annex.branchable.com/special_remotes/">special remotes</a>.
If it can store and retrieve objects by a key (an identifier usually based
on some hash), <code>git annex</code> can be taught to consider it external storage
and push files to it — without caring how it looks on the ‘other side’.</p>
<p>So let’s just make the other side that tablet?</p>
<p>Writing a new special remote is as easy as implementing the dedicated
<a href="https://git-annex.branchable.com/design/external_special_remote_protocol/">line protocol</a>,
which <code>git annex</code> uses to talk to a sub-process via std IO.</p>
<div class="side-note">
<p><span class="header">“as easy as”</span></p>
<p>of course belies that every line protocol will inevitably have
unspecified handling of white space</p>
<p>Presumably the world would be
a better place had we all learnt the art of never not bothering with a proper
grammar.</p>
<p>Alas!</p>
<p>(suffice it to say I had fun)</p>
</div>
<p>For extra fun, I decided to write mine in Rust, without any external
crates, i.e. only using things from <code>std</code> (calling a few external program
to handle e.g. ssh and uuids). This works surprisingly well: rust
makes for a – somewhat verbose – scripting language, too.</p>
<h3 id="xochitl-ipan-quixichihua-in-amatl">Xochitl ipan quixichihua in amatl</h3>
<h4 id="file-structure">File structure</h4>
<p>Xochitl, the tablet’s UI, expects documents to be stored under
<code>~/.local/share/remarkable/xochitl</code> as a flat list:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode shell"><code class="sourceCode shellsession"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">$ ls .local/share/remarkable/xochitl</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="co">-rw-r--r-- .local/share/remarkable/xochitl/d1e71a92-3ab8-4455-9233-d84c06f3997a.content</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="co">-rw-r--r-- .local/share/remarkable/xochitl/d1e71a92-3ab8-4455-9233-d84c06f3997a.local</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="co">-rw-r--r-- .local/share/remarkable/xochitl/d1e71a92-3ab8-4455-9233-d84c06f3997a.metadata</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="co">-rw-r--r-- .local/share/remarkable/xochitl/d1e71a92-3ab8-4455-9233-d84c06f3997a.pagedata</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="co">-rw------- .local/share/remarkable/xochitl/d1e71a92-3ab8-4455-9233-d84c06f3997a.pdf</span></span></code></pre></div>
<p>One displayed document is several files, grouped by a common UUID — the pdf
file itself, metadata, hand-drawn notes for each page of the document, tags, …</p>
<p>To store a new
one, the special remote can derive a stable UUIDv5 from the item’s key, namespaced
to the special remote itself (conveniently, <code>git annex</code> assigns each special
remote instance its own UUID already, which works well for this).</p>
<h4 id="file-content">File content</h4>
<p>Second step: minimal skeletons of the other files, just enough to make xochitl
happy and display our document. It turns out a few fields in <code>.metadata</code> and
<code>.content</code> are enough; xochitl will fill in the rest by itself.</p>
<div style="display:flex;padding:0 1em">
<style>
pre { margin: 0; height: 100%; align-content: center; }
div.sourceCode { height: calc(100% - 3em); margin: 0; padding: 1em; }
figure { padding: 0 1em; margin: 0; }
</style>
<figure style="flex:1">
<div class="sourceCode" id="cb2"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;createdTime&quot;</span><span class="fu">:</span> <span class="st">&quot;970351200&quot;</span><span class="fu">,</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;lastModified&quot;</span><span class="fu">:</span> <span class="st">&quot;978303600&quot;</span><span class="fu">,</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;lastOpened&quot;</span><span class="fu">:</span> <span class="st">&quot;{time}&quot;</span><span class="fu">,</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;lastOpenedPage&quot;</span><span class="fu">:</span> <span class="dv">0</span><span class="fu">,</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;parent&quot;</span><span class="fu">:</span> <span class="st">&quot;&quot;</span><span class="fu">,</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;pinned&quot;</span><span class="fu">:</span> <span class="kw">false</span><span class="fu">,</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;type&quot;</span><span class="fu">:</span> <span class="st">&quot;DocumentType&quot;</span><span class="fu">,</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;visibleName&quot;</span><span class="fu">:</span> <span class="st">&quot;Nahuatl As Written&quot;</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<figcaption>
Example minimal <code>.metadata</code>
</figcaption>
</figure>
<figure style="flex:1">
<div class="sourceCode" id="cb3"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;coverPageNumber&quot;</span><span class="fu">:</span> <span class="dv">0</span><span class="fu">,</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;documentMetadata&quot;</span><span class="fu">:</span> <span class="fu">{},</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;extraMetadata&quot;</span><span class="fu">:</span> <span class="fu">{},</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;fileType&quot;</span><span class="fu">:</span> <span class="st">&quot;pdf&quot;</span><span class="fu">,</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;fontName&quot;</span><span class="fu">:</span> <span class="st">&quot;&quot;</span><span class="fu">,</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;pageTags&quot;</span><span class="fu">:</span> <span class="ot">[]</span><span class="fu">,</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;tags&quot;</span><span class="fu">:</span> <span class="ot">[]</span><span class="fu">,</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;textAlignment&quot;</span><span class="fu">:</span> <span class="st">&quot;justify&quot;</span><span class="fu">,</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;textScale&quot;</span><span class="fu">:</span> <span class="dv">1</span><span class="fu">,</span></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;zoomMode&quot;</span><span class="fu">:</span> <span class="st">&quot;bestFit&quot;</span></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<figcaption>
Corresponding dummy <code>.content</code>
</figcaption>
</figure>
</div>
<p>One issue: <code>.metadata</code> sets the document’s name, as shown on the tablet.
But a special remote is nothing more than a
hash table: it knows items by key, not by any title or name, and won’t be told
anything as helpful as a file name.</p>
<h4 id="document-names">Document names</h4>
<p>So where to get a recognisable name?</p>
<p>I tried extracting the pdf’s embedded title, but it turns out this is too often
missing or unhelpful (who needs tens of documents titled “Microsoft Word
Document” in their listing?).</p>
<p>At this point I got a little stuck, but luckily, the protocol’s spec has
<a href="https://git-annex.branchable.com/design/external_special_remote_protocol/export_and_import_appendix/">an ‘export/import’ appendix</a>,
for this exact case: special remotes which also look like file listings, not only like
hash tables.</p>
<p>Its operations are broadly similar to their ‘normal’ store/retrieve siblings,
but each also receives a file name to be freely used by the remote. For the tablet,
just taking the file name (without extension) seems good enough.
All this requires is that the special remote is initialised with <code>exporttree=yes</code>. Problem solved?</p>
<p>… well, mostly. This appendix is only half-done, and only the export operations
are specified, testable, and implemented in <code>git annex</code>; the whole ‘import’
section is an unimplemented draft.</p>
<p>I hereby declare getting files back <em>out</em> from the tablet to be a problem for
future me.</p>
<h4 id="pushing-to-the-device">Pushing to the device</h4>
<p>Having set up the file structure, all that’s left is funneling it over ssh.
Thankfully, this is as easy as it possibly could be:</p>
<figure>
<img src="/images/remarkable-copyright.jpg" alt="An unexpected glimpse into a world where the GPL actually did what it was meant for (I wish more manufacturers did this!)" />
<figcaption aria-hidden="true">An unexpected glimpse into a world where the GPL actually did what it
was meant for (I wish more manufacturers did this!)</figcaption>
</figure>
<p>With all this done, it’s time to <code>git annex testremote</code> (but do it in a sandbox
— or a VM test — else there’s a good chance of leaving litter behind if
anything was wrong).</p>
<h3 id="git-annex-export-git-annex-wanted">git annex export &amp; git annex wanted</h3>
<p>One final issue: using the export operations allows us to use file names,
but the usual <code>git annex copy</code> &amp; friends now no longer work: Any special remote
initialised with <code>exporttree=yes</code> has to be used with <code>git annex export</code>, which
can only operate a git branch or revision (or a subtree thereof), but <em>not</em> on
a single file.</p>
<p>Happily, <code>git annex</code> has a concept of filtering, which the export will respect:</p>
<div class="side-note">
<p>Thanks to a friend for pointing this out! I was getting quite frustrated when
I discovered this.</p>
</div>
<p><code>git annex metadata</code> can attach little tags to files in git annex’s repository.</p>
<p><code>git annex wanted</code> lets one specify which files a remote “wants” to store;
files that don’t match aren’t exported.</p>
<p>So all I need to do is <code>git annex wanted &lt;specialremote&gt; "metadata=amatl=store"</code>
once, and then <code>git annex metadata -s amatl=store &lt;filename&gt;</code> to mark new files
for transfer, and then <code>git annex export</code> (or simply <code>git annex sync</code>) to push
all new files to the device.</p>
<h2 id="all-done">All done?</h2>
<p>Well, not quite. Now I can read pdfs, but would it not be nice to also get any
notes I draw on them back into the repository?</p>
<p>As mentioned, the “import” section of the protocol spec is still a draft, and
unimplemented; there’s some additional complexity in that the tablet’s format
for drawings is prorietary, although people
<a href="https://github.com/ax3l/lines-are-beautiful">have reverse-engineered it</a>.</p>
<p>Perhaps I’ll look at this at some point in the future; I’m not sure if doing this
via <code>git annex import</code> wouldn’t be stretching the concept of special remotes a
little too far — do I really want to replace the entire content-addressed pdf in
git annex’s store every time I draw a new line on it? — but for now, that’s it.</p>
]]></description>
    <pubDate>Sat, 25 Jan 2025 00:00:00 UT</pubDate>
    <guid>https://stuebinm.eu/posts/git-annex-xochitl.html</guid>
    <dc:creator>terru</dc:creator>
</item>
<item>
    <title>A line map for Milano's trams in T<sub>E</sub>X</title>
    <link>https://stuebinm.eu/posts/milano-tram-network.html</link>
    <description><![CDATA[<div class="toc-box"><div class="toc">

<ul>
<li><a href="#à-la-recherche-des-cartes-perdues" id="toc-à-la-recherche-des-cartes-perdues">À la recherche des cartes perdues</a>
<ul>
<li><a href="#official" id="toc-official">official</a></li>
<li><a href="#third-party" id="toc-third-party">third-party</a></li>
<li><a href="#historical" id="toc-historical">historical</a></li>
</ul></li>
<li><a href="#drawing-my-own" id="toc-drawing-my-own">Drawing my own</a>
<ul>
<li><a href="#sketching" id="toc-sketching">sketching</a></li>
<li><a href="#tikzpictures" id="toc-tikzpictures">tikzpictures</a></li>
<li><a href="#so-many-stations" id="toc-so-many-stations">so many stations …</a></li>
</ul></li>
<li><a href="#and-now" id="toc-and-now">And now?</a>
<ul>
<li><a href="#cant-we-automate-this" id="toc-cant-we-automate-this">can’t we automate this?</a></li>
<li><a href="#conclusion-advice-for-making-your-own" id="toc-conclusion-advice-for-making-your-own">conclusion &amp; advice for making your own</a></li>
</ul></li>
<li><a href="#references" id="toc-references">References</a></li>
</ul>
</div></div>
<div class="background">
<p><img src="/images/milano-map-wide.webp" /></p>
</div>
<!-- > Milan's huge tram system is too complex to be legibly displayed on the same -->
<!-- > map as the Metro and S lines. -->
<style scoped>
 .first-p {
  @media (prefers-color-scheme: dark) {
    margin-top: 7em;
  }
}
</style>
<p class="first-p">
About a month ago now, I spent a couple fun but exhausting days in Milano for a
conference. While there I <a href="https://link.springer.com/chapter/10.1007/978-3-031-71177-0_1">gave a talk</a>,
unexpectedly <a href="https://pleroma.stuebinm.eu/notice/AltsHzSZHUGtrc6o7s">found a yellow soft drink I miss a lot</a>,
and of course I also took the tram:
</p>
<figure>
<img src="/images/milano-tram-photo.jpg" alt="Exhibit A: a cute old tram  (with apologies for the poor quality, I’d left my camera at the hotel that day)." />
<figcaption aria-hidden="true">Exhibit A: a cute old tram <br>(with apologies for the poor quality, I’d left my camera at the hotel that day).</figcaption>
</figure>
<p>Milano is famous for still having a large number of <a href="https://en.wikipedia.org/wiki/ATM_Class_1500">old trams from the 1920ies</a>
running in regular service. And as one of the lines stops directly next
to the university campus, I decided to simply get on …</p>
<p>… to quickly discover I had no idea where it was going.
At the tram stop, there’d been an information sign for the two lines stopping at it,
but only with a list of stations (useless to non-local me, who recognised
none of them). So I looked for a map.</p>
<p>Without success! No map at the stop, no map in the tram, and (as I checked later) not
even at larger interchange stations. I got off near Cadorna, and then took the metro
back — but in the metro station, too, the maps showed only the metro itself and
the <a href="https://en.wikipedia.org/wiki/Milan_S_Lines">suburbano lines</a>, not the trams.</p>
<figure>
<img src="/images/milano-tram-uscita.jpg" alt="Should I get off here?" />
<figcaption aria-hidden="true">Should I get off here?</figcaption>
</figure>
<h2 id="à-la-recherche-des-cartes-perdues">À la recherche des cartes perdues</h2>
<p>So really, how does one find out which tram to take? Looking around, I did find
a couple options:</p>
<h3 id="official">official</h3>
<p>The city’s transit operator ATM has a <a href="https://giromilano.atm.it/assets/images/schema_rete_metro.jpg">map displayed at metro stations</a>,
but it only shows the metro and (the central parts of) the suburbano train lines.
They also have a couple other maps <span class="citation" data-cites="ATM">[<a href="#ref-ATM" role="doc-biblioref">ATM</a>]</span> listed on their website; confusingly this
includes a <a href="https://www.atm.it/it/ViaggiaConNoi/Documents/rete_notte.pdf">complete map of night lines</a>,
but no equivalent for ‘normal’ service hours.</p>
<p>Unfortunately I didn’t have the time to go bother them at an information desk while
I was in the city, so perhaps there are some maps available there — but if so, I’ve
not found any of them mentioned online.</p>
<p>As far as I can tell, currently the only official way to navigate the tram (and also
the much larger bus) network is via ATM’s mobile apps, or alternatively via its
web app <span class="citation" data-cites="webapp">[<a href="#ref-webapp" role="doc-biblioref">ATMApp</a>]</span>. But no matter how convenient these are for routing,
they don’t contain a good map either (and the web app likes to get stuck if one
tries to make it display more than a few tram lines at once — not that
it would produce a very readable map if it succeeds).</p>
<h3 id="third-party">third-party</h3>
<p>Finally, there are (of course) various tourist maps of the city’s center and
main sightseeing attractions, which sometimes also include the central section
of the tram network (although <a href="https://ontheworldmap.com/italy/city/milan/large-detailed-tourist-map-of-milan.html">not always in the most readable way</a>),
as well as countless third-party web sites and transport wikis containing variously up-to-date
information.</p>
<p>Even on WikiMedia (usually a good source of custom-drawn network maps,
often much nicer than their official versions) a cursory glance found only a
<a href="https://commons.wikimedia.org/wiki/Category:Tram_maps_of_Milan">few maps</a>
of the tram system. But mostly these are geographical maps first, with extra
information squished into them (although looking again I did find <a href="https://upload.wikimedia.org/wikipedia/commons/f/f7/Milan_public_transport_diagram.jpg">this very neat map</a>).</p>
<h3 id="historical">historical</h3>
<p>What came before the mobile apps? Surely people must’ve had a way to figure out
which tram to take? They did! Fortunately for us, stagniweb <span class="citation" data-cites="stagniweb">[<a href="#ref-stagniweb" role="doc-biblioref">Stagniweb</a>]</span> has preserved
an impressive collection
of archived historical maps of Milano’s trams, the last of which is dated to 1982:</p>
<figure>
<img src="/images/milano-atm-1982.jpg" alt="Map of Milano’s tram lines by ATM from 1982  (via stagniweb.it, which also has a higher-resolution scan and many other maps, go visit!)" />
<figcaption aria-hidden="true">Map of Milano’s tram lines by ATM from 1982 <br>(via <a href="https://www.stagniweb.it/foto6.asp?File=mappe_mi&amp;righe=1&amp;inizio=17&amp;InizioI=1&amp;RigheI=50&amp;Col=4">stagniweb.it</a>, which also has a higher-resolution scan and many other maps, go visit!)</figcaption>
</figure>
<p>Unfortunately, I’m unclear on what happened after this map — an urbanfile.org blog post <span class="citation" data-cites="urbanfile">[<a href="#ref-urbanfile" role="doc-biblioref">Montella</a>]</span>
mentions a similar map from 1985, but sometime after that, ATM seems to have
discontinued this format; I’ve not found any official map of the trams
from the years since.</p>
<p>By the time <a href="https://web.archive.org/web/20110701000000*/https://www.atm.it">archive.org</a>
starts having snapshots of ATM’s website in 2011 (before then, the domain belonged
to an online shopping business), they already offered the routing app, and seemingly
no full network map of the tram lines were available to download then, either —
although the site mentions you <em>could</em> still get free paper maps. But if these
still included the trams, I’ve not found any pictures of them online.</p>
<div class="side-note">
<p><span class="header">archive.org troubles</span></p>
<p>There might well be more information on the wayback machine; I’ve not looked as extensively
as I’d like, since it’s currently still very sluggish and often struggles to respond
to requests at all.</p>
</div>
<p>If anyone has clues on this (or even lived in or visited Milano during that time),
feel free to message me, I’d love to know!</p>
<h2 id="drawing-my-own">Drawing my own</h2>
<p>So there I was, sitting in my hotel room, and wondering how to procrastinate on
making the slides for my talk (thankfully scheduled for the last day of the
conference, so I had some time). Why not try to make my own map?</p>
<p>By complete coincidence, the weekend before I’d been at <a href="https://baytex.in-ulm.de/2024/">BayT<sub>E</sub>X</a>,
where Aada had given an amazing last-minute talk <span class="citation" data-cites="aada">[<a href="#ref-aada" role="doc-biblioref">Aada</a>]</span>
about how to draw line network maps with T<sub>E</sub>X, so I had a rough idea
of how to go about it.</p>
<h3 id="sketching">sketching</h3>
<p>Lacking an official map to get any kind of overview, I fought ATM’s
web app for a while (it kept getting stuck) until I had a rough sense where each
line goes, and where they meet and cross each other:</p>
<figure>
<img src="/images/milano-trams-sheet1.jpg" class="half" />
<img src="/images/milano-trams-sheet2.jpg" class="half" />
<figcaption>
Rough hand-drawn sketches to figure out where all these trams go.
</figcaption>
</figure>
<p>And having done that I thought, well, really, how hard can it be to do this
with TikZ? All I have to do is to re-use
<a href="https://gitlab.com/ada.loveless/tex-networks/">Aada’s framework and shapes</a>, surely
the rest is doable, too?</p>
<h3 id="tikzpictures">tikzpictures</h3>
<p>Aada’s maps rely on <a href="https://ctan.org/pkg/pgf">tikzpictures</a> to do most of
the heavy lifting involved in creating pictures.</p>
<div class="side-note">
<p><img src="/images/milano-trams-rights-duck.svg" style="width:10em" /></p>
</div>
<p>Depending on your background, you might have a certain intuitive aversion to TikZ
— or at least I’ve found that many academics who use T<sub>E</sub>X for their papers
are wary of it, and its 1300+ page manual has a reputation of its own – but it is
(surprisingly?) one of the least bad tools for this kind of task:</p>
<ul>
<li>compared to most
(vector-)graphics programs, there’s a usable macro system.</li>
<li>it’s possible to build (albeit leaky) abstractions over the shapes used to
draw the map.</li>
<li>drawings made with TikZ are parametric: nodes can be set
relative to other nodes, so the network’s layout stays relatively flexible
even with all lines and stations filled in.</li>
</ul>
<!-- TODO: possible to draw the dependencies inside the map? -->
<p>A decision I made very early on was to use lines 9 and 10 as “anchors” for the
rest of the network, as they form a nice circle around the city’s center. This
wasn’t the best idea — it kept getting crowded when I filled in the central stations
later — so if you intend to design your own map, I’d advise to grow it “outwards”
instead: start with the most complex central stations, and then slowly add
the rest around them.</p>
<p>Of course, using TikZ for this sort of thing does have its annoyances:</p>
<ul>
<li>most obviously, it involves writing text; results are only visible after calling T<sub>E</sub>X.</li>
<li>there’s thus all the usual problems that come with writing several
hundred lines of code in any language; it’ll easily turn into spaghetti.
Though if that happens, one can always define more macros …</li>
<li>the biggest by far: T<sub>E</sub>X is fundamentally unsuitable for creating
true, non-leaky abstractions (barring some hackery with catcodes, as e.g. expl3 does),
and various macros defined by TikZ have strange, unexpected limitations
— for example, a <code>\\</code> occurring inside a <code>\foreach</code>
will usually break things, especially if it is text inside a node.</li>
</ul>
<div class="side-note">
<p><span class="header">\pgfplotsforeachungrouped</span></p>
<p>The “<code>\\</code> inside <code>\foreach</code>” problem turns out to be distinct from the more common
“<code>\\</code> inside a node” problem, which is usually solved by simply giving the node
a text alignment scheme and an explicit width; something else inside the <code>\foreach</code>
also seems to break whenever there is a <code>\\</code> in an iteration’s output group.</p>
<p>There are Workarounds for this, but these involve liberal use of <code>\edef</code> / <code>\expandafter</code>.</p>
<p>There
is a <code>\pgfplotsforeachungrouped</code> which is more robust against this kind of thing,
but which also does not support multiple loop variables (although the pgfplots
documentation appears to imply it should). However, given the sometimes absurdly
long station names I desperately needed line breaks inside labels.</p>
<p>In the end my solution was to write a little expl3 function which replaces
<code>|</code> by <code>\\</code>, and that does work ..</p>
</div>
<p>Unfortunately it turns out drawing one of the largest tram networks in Europe is
not doable in an evening in a hotel, so I left Milano (and the conference)
with a very unfinished map. But over the following weeks I kept coming back to it,
fiddling around until the overall layout
<a href="https://pleroma.stuebinm.eu/notice/AmVpMtPMk3Puqeej4K">looked mostly usable</a>.</p>
<h3 id="so-many-stations">so many stations …</h3>
<p>After that TikZ could really shine: filling in all the other stations was a breeze,
and done in only a couple hours (most of which were spent learning just enough
expl3 to build a useful macro for nicely setting multi-line labels in odd locations
from inside a <code>\foreach</code>).</p>
<figure>
<img src="/images/milano-trams.svg" alt="A map of Milano’s tram lines, of dubious accuracy (Source Code)." />
<figcaption aria-hidden="true">A map of Milano’s tram lines, of dubious accuracy (<a href="https://stuebinm.eu/git/tex-networks/tree/milano/trams.tex">Source Code</a>).</figcaption>
</figure>
<div class="side-note">
<p><a href=".header">data issues</a></p>
<p>I say “lacking some information here,” but really what’s going on is that <span class="citation" data-cites="webapp">[<a href="#ref-webapp" role="doc-biblioref">ATMApp</a>]</span>
only ever shows you real-time data. In case of a diversion or temporary line closure,
there will be a free-form text describing the disruption, but no information on
the intended, original route.</p>
<p>In some places, I cobbled the information I needed together from <span class="citation" data-cites="osm">[<a href="#ref-osm" role="doc-biblioref">OSM</a>]</span> and <span class="citation" data-cites="wikiroutes">[<a href="#ref-wikiroutes" role="doc-biblioref">WiRo</a>]</span>,
even though the latter of these is at least partially out of date.</p>
</div>
<p>And well, here it is. A mostly-accurate map of Milano’s trams. While it has a few
known defects (some routes take a slightly different route in either direction,
and sometimes there was some guessing involved due to <span class="citation" data-cites="webapp">[<a href="#ref-webapp" role="doc-biblioref">ATMApp</a>]</span> lacking some
information), I am overall quite happy with the result:</p>
<ul>
<li>The overall layout of the lines is very clear, with geographic convolutions
smoothed out in favour of their topology.</li>
<li>I could entirely avoid rotating text even for very long station names (“Piazzale
Principessa Clotilde Ospedale Fatebenefratelli”), which I’ve never found
aesthetically pleasing.
(I also avoided the common Italian shortenings like “P.le.” for “Piazzale”
simply because I discovered I could.)</li>
<li>Despite some complications, it’s (almost) always easy to see which station a
label belongs to, and the labels mostly don’t overlap anything else.</li>
</ul>
<h2 id="and-now">And now?</h2>
<p>Some information essential to a usable transit map is still missing: there probably
ought to be a legend, stations outside the City of Milano should probably be
marked as such, fare zones and interchanges to other modes of transport could be
added in … but I don’t really have plans to add these for now; the fun for me was
in drawing the network itself.</p>
<div class="side-note">
<p><span class="header">another map</span></p>
<p>And of course, having finally finished drawing the network, I find that somebody else had already
done it: there’s a map I somehow missed before on reddit <span class="citation" data-cites="reddit">[<a href="#ref-reddit" role="doc-biblioref">Santín</a>]</span>.</p>
<p>Not only does it show all tram lines, but also the central sections of both metro
and suburbano lines.</p>
<p>It’s been fascinating to compare to my own map, and re-discover
some of my own choices in it — but also decisions done differently: 9 and 10 do
not form a circle, the lines around Duomo and Cordusio are handled differently, etc. …</p>
</div>
<p>Perhaps I could now integrate both metro and suburbano into my map as well;
they both intersect with the tram network heavily, and are also useful
transit modes inside the city’s core. Otoh there’s very little
chance I will ever add the bus lines …</p>
<h3 id="cant-we-automate-this">can’t we automate this?</h3>
<p>Several people, when seeing what I was working on, immediately asked: why have
you written 1500 lines of T<sub>E</sub>X for this, a task which surely can be
automated?</p>
<p>I think there’s two answers to this: first, this kind of ‘planarisation’ isn’t really
easy at all. For one, it’s unlikely the network graph is planar, thus choices
must be made on where it should overlap itself. But there’s a lot more:
how much should it abstract, how true should it be to geography? How and
where to place labels, and how to make them fit next to a well-connected interchange?
Nor is planarity the same a clarity: even if no lines ever cross over at all,
a reader might still easily get ‘lost’ when attempting to understand the network depicted
by the map.</p>
<p>Thus, secondly: while we can insist on imagining a transit map as the
result of a large optimisation problem, in the end there’s a lot more of art to
designing it than there is graph theory. After all, its purpose <em>isn’t</em> to depict
a graph, but to communicate helpful information to people who don’t know their
way around an unfamiliar city. That it depicts a graph is only incidental.</p>
<p>This is not to say we can’t perhaps make the process of designing such a map a
little less onerous. There’s some fun ways of drawing transit lines onto geography
<span class="citation" data-cites="transitapp">[<a href="#ref-transitapp" role="doc-biblioref">trapp</a>]</span>, or even automating the entire thing, but in an interactive way so
a person can still decide on at least some of the ‘choices’ made inside the map <span class="citation" data-cites="janiak">[<a href="#ref-janiak" role="doc-biblioref">Janiak</a>]</span>.</p>
<h3 id="conclusion-advice-for-making-your-own">conclusion &amp; advice for making your own</h3>
<p>Finally, perhaps you feel — for whatever reason — the insatiable need to create a
similar map yourself. Perhaps your local city lacks a good map (or any map).
Perhaps it does have a map, but it’s outright wrong in how it displays some lines
(this is waay more common than I used to think). Regardless, here (in no particular
order) are some useful things I learnt along the way:</p>
<ul>
<li>Drawing a rough sketch by hand first turns out to be a very good idea. Whatever
program you use to draw the graphic later, it’s always clunky to re-organise
many lines at once; starting a new, improved rough sketch is easier.</li>
<li>This is doubly true if you don’t have an existing map to guide you.</li>
<li>Your final map will look very different than your sketch. Start with the most
difficult, most densely clumped section of the network, it’ll change the most
(and afterwards you can be glad to have that behind you)</li>
<li>don’t forget to think about where labels go; compared to lines / stations, they
take up a lot of space!</li>
<li>If you’re using TikZ, it pays to have several “anchor” stations, and place everything
else relative to these in an outwards-growing dependency tree.</li>
<li>Also if using TikZ: read a little of the manual first! Yes, it does have 1300+
pages. But it’s really useful to know about all the fun ways to specify
coordinates.</li>
<li>Learn some expl3 while you’re at it, and try not to use the pgf <code>\foreach</code>
unless you really need to, it’s brittle and will break on you later in
entirely unexpected ways.</li>
</ul>
<p>And most importantly, have fun with it! Don’t be afraid to make
unexpected design choices and see where they lead you! And if you get stuck,
nerdsnipe some of you friends into looking at it; transit maps turn out to make
for a fun social activity — I’d especially like to thank io for so many good
suggestions for my own map, along with Aada for having the initial idea of drawing
these with T<sub>E</sub>X.</p>
<h2 class="unnumbered" id="references">References</h2>
<div id="refs" class="references csl-bib-body" data-entry-spacing="0" role="list">
<div id="ref-aada" class="csl-entry" role="listitem">
<div class="csl-left-margin">[Aada] </div><div class="csl-right-inline">Aada, Liniennetzpläne, in T<sub>E</sub>X! BayT<sub>E</sub>X, 2024-09-07. <a href="https://baytex.in-ulm.de/2024/slides/liniennetzplaene.pdf">https://baytex.in-ulm.de/2024/slides/liniennetzplaene.pdf</a></div>
</div>
<div id="ref-ATM" class="csl-entry" role="listitem">
<div class="csl-left-margin">[ATM] </div><div class="csl-right-inline">ATM, Azienda Trasponti Milanesi, Mappe e Servizi. 2024. <a href="https://www.atm.it/it/AltriServizi/Trasporto/Pagine/Mappe.aspx">https://www.atm.it/it/AltriServizi/Trasporto/Pagine/Mappe.aspx</a></div>
</div>
<div id="ref-webapp" class="csl-entry" role="listitem">
<div class="csl-left-margin">[ATMApp] </div><div class="csl-right-inline">ATM, Azienda Trasponti Milanesi, GiroMilano. 2024. <a href="https://giromilano.atm.it">https://giromilano.atm.it</a></div>
</div>
<div id="ref-janiak" class="csl-entry" role="listitem">
<div class="csl-left-margin">[Janiak] </div><div class="csl-right-inline">Tim Janiak, Interactive Design of Metro Maps: Master’s thesis. Julius-Maximilians-Universität Würzburg, 2021. <a href="https://www1.pub.informatik.uni-wuerzburg.de/pub/theses/2021-janiak-masterarbeit.pdf">https://www1.pub.informatik.uni-wuerzburg.de/pub/theses/2021-janiak-masterarbeit.pdf</a></div>
</div>
<div id="ref-urbanfile" class="csl-entry" role="listitem">
<div class="csl-left-margin">[Montella] </div><div class="csl-right-inline">Marco Montella, Milano | Trasporti - Atm presenta la nuova mappa della metropolitana per il 2021: Con novità inedite. 2021-01-19. <a href="https://blog.urbanfile.org/2021/01/19/milano-trasporti-atm-presenta-la-nuova-mappa-della-metropolitana-per-il-2021-con-novita-inedite/">https://blog.urbanfile.org/2021/01/19/milano-trasporti-atm-presenta-la-nuova-mappa-della-metropolitana-per-il-2021-con-novita-inedite/</a></div>
</div>
<div id="ref-osm" class="csl-entry" role="listitem">
<div class="csl-left-margin">[OSM] </div><div class="csl-right-inline">OpenStreetMap contributors, OpenStreetMap. <a href="https://osm.org">https://osm.org</a></div>
</div>
<div id="ref-reddit" class="csl-entry" role="listitem">
<div class="csl-left-margin">[Santín] </div><div class="csl-right-inline">Raül Santín, Milano Tram and Urban Network Diagram [OC]. 2019-12-28. <a href="https://old.reddit.com/r/milano/comments/egl4wu/milano_tram_and_urban_network_diagram_oc">https://old.reddit.com/r/milano/comments/egl4wu/milano_tram_and_urban_network_diagram_oc</a></div>
</div>
<div id="ref-stagniweb" class="csl-entry" role="listitem">
<div class="csl-left-margin">[Stagniweb] </div><div class="csl-right-inline">stagniweb.it, Tram a Milano (1914-1982): L’album fotografico. <a href="https://www.stagniweb.it/foto6.asp?File=mappe_mi&amp;Tipo=index&amp;Righe=50&amp;Col=4">https://www.stagniweb.it/foto6.asp?File=mappe_mi&amp;Tipo=index&amp;Righe=50&amp;Col=4</a></div>
</div>
<div id="ref-transitapp" class="csl-entry" role="listitem">
<div class="csl-left-margin">[trapp] </div><div class="csl-right-inline">transitapp.com, A Technical Follow-Up: How We Built the World’s Prettiest Auto-Generated Transit Maps. 2016-10-04. <a href="https://blog.transitapp.com/how-we-built-the-worlds-prettiest-auto-generated-transit-maps-12d0c6fa502f">https://blog.transitapp.com/how-we-built-the-worlds-prettiest-auto-generated-transit-maps-12d0c6fa502f</a></div>
</div>
<div id="ref-wikiroutes" class="csl-entry" role="listitem">
<div class="csl-left-margin">[WiRo] </div><div class="csl-right-inline">WikiRoutes, List of Milan public transport routes. 2024. <a href="https://wikiroutes.info/en/milan/catalog">https://wikiroutes.info/en/milan/catalog</a></div>
</div>
</div>
]]></description>
    <pubDate>Thu, 24 Oct 2024 00:00:00 UT</pubDate>
    <guid>https://stuebinm.eu/posts/milano-tram-network.html</guid>
    <dc:creator>terru</dc:creator>
</item>
<item>
    <title>Am selben Bahnsteig gegenüber?</title>
    <link>https://stuebinm.eu/posts/am-selben-bahnsteig-gegen%C3%BCber.html</link>
    <description><![CDATA[<div class="toc-box"><div class="toc">

<ul>
<li><a href="#the-shape-of-the-problem" id="toc-the-shape-of-the-problem">The Shape of the Problem</a></li>
<li><a href="#related-work" id="toc-related-work">Related Work</a></li>
<li><a href="#getting-raw-data" id="toc-getting-raw-data">Getting raw data</a>
<ul>
<li><a href="#data-model" id="toc-data-model">Data Model</a></li>
<li><a href="#overpassql" id="toc-overpassql">OverpassQL</a></li>
<li><a href="#writing-queries" id="toc-writing-queries">Writing Queries</a></li>
<li><a href="#testing" id="toc-testing">Testing</a></li>
</ul></li>
<li><a href="#catch-them-all" id="toc-catch-them-all">Catch them all?</a>
<ul>
<li><a href="#names-are-hard" id="toc-names-are-hard">Names are hard</a></li>
<li><a href="#so-many-betriebsstellen" id="toc-so-many-betriebsstellen">So many Betriebsstellen</a></li>
<li><a href="#some-results" id="toc-some-results">Some Results</a></li>
</ul></li>
<li><a href="#bahnhof.name" id="toc-bahnhof.name">Bahnhof.name</a></li>
<li><a href="#possible-improvements" id="toc-possible-improvements">Possible improvements</a></li>
<li><a href="#conclusion" id="toc-conclusion">Conclusion</a></li>
<li><a href="#references" id="toc-references">References</a></li>
</ul>
</div></div>
<blockquote>
<p>Connection to ICE 4711 today on track 8, on the same platform directly
opposite.</p>
</blockquote>
<p>How reassuring to hear these words — but often they’re not there. Perhaps there
was a surprising last-minute track change, or train staff isn’t sure either,
or someone simply forgot, or you’re on a regional line where this kind of
announcement is simply never done at all. Or perhaps (my personal favorite) the
connection you’re intending to take was never meant to be possible in the first
place.</p>
<div class="side-note">
<p><span class="header">Caveat:</span></p>
<p>To tame the otherwise overwhelm­ingly large scope, this post will limit
itself to stations operated by Deut­sche Bahn, i.e. mostly stations in Ger­many.</p>
</div>
<p>And suddenly you <em>really</em> need to know if tracks 3 and 4 are on the same platform
in a station you’ve never been in before.</p>
<p>This post will describe how to solve this problem using data from Open Street
Map (OSM), and use it as an excuse to learn some OverpassQL along the way. Since
I happened to already run a search engine for German railway stations (who doesn’t?),
you can also use the result simply by visiting <a href="https://bahnhof.name">bahnhof.name</a>
to get a list of platforms for your favorite station.</p>
<h2 id="the-shape-of-the-problem">The Shape of the Problem</h2>
<p>For the benefit of everyone who does not feel an innate sense of comfort in the
liminal spaces which lurk at the edges of large rail yards (are you, perhaps,
normal about trains?), I should perhaps explain how this is a problem at all.
Because, well, aren’t tracks just numbered?</p>
<p>And indeed, usually they are. Occasionally even sequentially. Except when
they’re not – almost all railway stations in Europe are a confusing,
organically-grown mess, often more than a century old, and have had tracks
removed, added, renamed, jumbled around, re-purposed, or even forgotten about
entirely several times already.</p>
<p>So while tracks are usually numbered, sometimes track 3 simply no longer exists
(but track 4 does). Or track 3 does exist, but is now only used for trains
going through the station without stopping, and no longer has a platform next to
it. Maybe there’s an additional track 1a at the far end of
the platform with track 1. Or maybe track 1a just means one half of track 1,
and the other half is called 1b. Maybe there is a track 108 next to track 8.
Or maybe there is a track 101 at the far end of the station, for no obvious
reason at all.</p>
<p>Or maybe you’ve stumbled into an unforgiving beast such as Kaiserslautern
Hauptbahnhof, which has tracks 1 through 5, tracks 8 and 10, and also 39, 40, 41,
42, <em>45</em>, and 120. Good luck guessing which of these are at the same platform (or
even how the platforms are positioned relative to each other) if you’ve never
seen it on a map. The signage at the station can only help so much.</p>
<figure>
<img src="/images/skl.png" class="var-oversized" alt="Station Layout of Kaiserslautern Hbf. Woe to the unprepared!   (and thanks to the people who mapped this in OSM!)" />
<figcaption aria-hidden="true">Station Layout of Kaiserslautern Hbf. Woe to the unprepared! <br>
(and thanks to the people who mapped this in OSM!)</figcaption>
</figure>
<p>So it seems worthwhile to invest a little time into this problem. Surely it’s
possible to find an easier way to answer “are these two tracks next to each
other” than fiddling around with maps and signage?</p>
<h2 id="related-work">Related Work</h2>
<p>The best all-in-one free tool available for navigating stations that I know of is
<a href="https://apps.kde.org/itinerary/">KDE Itinerary</a>. While it focuses on general
travel-planning, ticket-bookkeeping, and basically absorbing every use-case
of any train operator’s in-house journey app except literally selling you tickets,
it also displays station layouts, which it sources from Open Street Map (OSM).</p>
<p>Platform numbers are highlighted, complex multi-level station building layouts are
displayed nicely, and whereever it can it integrates real-time APIs of station
operators to display things like which elevators are currently out of order.
More importantly for our problem, it can also give a list
of all tracks in the station; useful for multi-level stations like Berlin Hbf,
where not all platforms are immediately visible on a map.</p>
<div class="side-note">
<p><span class="header">DB InfraGO</span></p>
<p>But not for long! Starting next year, we’ll get <a href="https://www.deutschebahn.com/de/presse/pressestart_zentrales_uebersicht/DB-Aufsichtsrat-bringt-gemeinwohlorientierte-Infrastrukturgesellschaft-DB-InfraGO-AG-auf-den-Weg-11872708">yet another reshuffle
of DB’s organisational chart</a>,
and DB Station &amp; Service will cease to exist.</p>
</div>
<p>The official <a href="https://bahnhof.de">bahnhof.de</a> website run by DB Station &amp; Service,
who operate the public-facing side of almost all train stations in Germany,
likewise offer interactive station maps. These are again heavily based on OSM,
but also source some of their information from elsewhere, presumably from DB
itself. Notably, for larger stations they often include platform sections,
information which is frequently absent in OSM. But more observations on that
<a href="#some-results">a little later</a>.</p>
<p>Yet it misses out on some of KDE Itinerary’s nicest features: no live elevator status,
no search for a given platform number, and no links to elsewhere.
For a few stations, such as Berlin Haupt­bahn­hof,
<a href="https://www.bahnhof.de/downloads/station-plans/1071.pdf">they additionally offer PDF maps</a>,
but these are rare.</p>
<p>Confusingly, they also don’t seem to advertise this website very much – multiple
people I spoke to had no idea that it exists at all, even when they said it
would’ve been very useful for them in the past.</p>
<p>Many other trip planning apps offer similar features, but are usually not nearly as
sophisticated about it — e.g. <a href="https://gitlab.com/oeffi/oeffi/">Oeffi</a> simply
offers to open the station’s position in any third-party maps app that is installed.</p>
<p>Unfortunately, the only app I know to do actual <em>in-station</em> routing is
<a href="https://www.sbb.ch/de/reiseinformationen/apps/sbb-mobile.html">SBB’s official mobile app</a>,
where that feature is limited to stations actually run by SBB itself.</p>
<figure>
<img src="/images/bahnsteig-other-apps.png" alt="SBB Mobil and KDE Itinerary view of the same station. KDE Itinerary shows a lot more information (platform numbers, shops, tracks). SBB Mobil focuses on showing only the suggested route for an interchange from a train platform through the main station building to a bus stop." />
<figcaption aria-hidden="true">SBB Mobil and KDE Itinerary view of the same station. KDE Itinerary shows a lot
more information (platform numbers, shops, tracks). SBB Mobil focuses on showing
only the suggested route for an interchange from a train platform through the
main station building to a bus stop.</figcaption>
</figure>
<p>And while all of them can be used to answer the question “are these tracks
opposite of each other?”, they all do so as side-product of focusing on adjacent,
more general themes. So why not build our own, special-purpose thing?</p>
<h2 id="getting-raw-data">Getting raw data</h2>
<div class="side-note">
<p><span class="header">NeTEx</span></p>
<p>In fact, there is a second alternative approach: EU Regulation 2017/1926 <span class="citation" data-cites="eureg">[<a href="#ref-eureg" role="doc-biblioref">EU17</a>]</span> requires
all operators of public transport within the EU to publish
timetable data in the <a href="https://www.netex-cen.eu/">NeTEx</a> format, aggregated into
one large data set per country. The European Commission maintains
<a href="https://transport.ec.europa.eu/transport-themes/intelligent-transport-systems/road/action-plan-and-directive/national-access-points_en">a list</a>.</p>
<p>Unfortunately, these are true beasts: for Germany, it comes in at over 33GiB of
pure XML, with wildly differing level of detail depending on line operators.</p>
<p>Perhaps I’ll manage to do something with it yet, but if so, that’s for a future
post.</p>
</div>
<p>Since using OSM data seems to work out nicely for KDE Itinerary, I will here
continue with that idea.</p>
<p>There is one pretty obvious alternative: Deutsche Bahn does run its own Open Data portal.
It does not contain much, but there is in fact a data set
describing platforms <span class="citation" data-cites="Bahnsteigdaten">[<a href="#ref-Bahnsteigdaten" role="doc-biblioref">DBStus20a</a>]</span>. Unfortunately, it has last been updated
in 2020, and there seems little hope of regular yearly updates returning (my email
about this went unanswered); and unlike with OSM, where the platforms are part
of a much richer mapping effort, this data stands alone — so if we did use it,
there’d be no easy way to connect it to anything else.</p>
<h3 id="data-model">Data Model</h3>
<p>OSM has three kinds of objects: <em>nodes</em>, <em>ways</em>, and <em>relations</em>, which can all
have tags attached to them. Additionally, there is a <em>membership</em> relation
between objects. Train stations usually look something like this:</p>
<ul>
<li>Individual platforms each get their own object, which have a <code>ref</code> or <code>local_ref</code> tag
giving their name (the difference between these two seems a little unclear;
both are in frequent use for the same thing). For platforms which have no name
(basically all in Germany) this is a semicolon-separated list of track numbers.</li>
<li>Platform edges are often (but not as universally) also mapped, and are then
set as members of the platform.</li>
<li>Platforms are part of a relation, which collects everything belonging to an
entire station (along with walkways, shops, stairs, buildings, etc.).</li>
<li>Stations also usually have a single “meta” node, on which we can find the
station’s name and various kinds of IDs. We will use the <code>railway:ref</code> tag,
which contains “the operator’s internal abbreviation” of this station, and is
widely mapped in OSM.</li>
<li>Finally, some stations are more complex than others: they may be split into
smaller stations, have a local tram stop associated with them, or any manner
of other things, which all result in a deeper nesting of membership
relations.</li>
</ul>
<p>So all we have to is to find a station’s meta node, walk through all the
potentially messy forest of objects it’s associated with, and then grab
everything that looks like a platform.</p>
<h3 id="overpassql">OverpassQL</h3>
<div class="side-note">
<p><span class="header">OverpassXML</span></p>
<p>Actually, there is a second language, which the wiki suggest one use instead.
But it’s XML-based and … eh, let’s look at the other thing instead.</p>
</div>
<p>It turns out that there is an entire language which exists solely to query data
in OSM, which I had never encountered before.</p>
<p>Some of its design choices feel a little arcane (why does it limit almost all
functions to names which are at most three characters long?), but otherwise
I found it very pleasant to use.</p>
<h3 id="writing-queries">Writing Queries</h3>
<div class="side-note">
<p><span class="header">Learning Resources</span></p>
<p>The main wiki pages to read on the query language are the one on
<a href="https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL">Over­passQL</a> itself
as well as its <a href="https://wiki.openstreetmap.org/wiki/Overpass_API/Language_Guide">Language Guide</a>.</p>
<p><span class="citation" data-cites="Hann">[<a href="#ref-Hann" role="doc-biblioref">Hann20</a>]</span> also gives an introduction into the language. I wish I’d stumbled across
this post before fiddling everything out using just the wiki.</p>
</div>
<p>A first attempt, using München Haupt­bahn­hof as a test case:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode overpassql"><code class="sourceCode overpassql"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[out:json][timeout:25]</span><span class="op">;</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="dt">node</span><span class="kw">[</span><span class="st">&quot;railway:ref&quot;</span><span class="kw">=</span><span class="st">&quot;MH&quot;</span><span class="kw">][</span><span class="st">&quot;operator&quot;</span><span class="kw">~</span><span class="st">&quot;^DB&quot;</span><span class="kw">]</span><span class="op">;</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="dt">rel</span><span class="kw">[</span><span class="st">&quot;public_transport&quot;</span><span class="kw">=</span><span class="st">&quot;stop_area&quot;</span><span class="kw">](</span><span class="dt">&lt;</span><span class="kw">)</span><span class="op">;</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="dt">nwr</span><span class="kw">[</span><span class="st">&quot;railway&quot;</span><span class="kw">=</span><span class="st">&quot;platform&quot;</span><span class="kw">](</span><span class="dt">&gt;&gt;</span><span class="kw">)</span><span class="op">;</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="dt">out geom</span><span class="op">;</span></span></code></pre></div>
<p>It’s pretty easy to read, line-by-line: first we define general query parameters,
such as the output format. After that, each line defines a statement which selects
something.</p>
<ol type="1">
<li>Select any node with <code>railway:ref</code> set to <code>MH</code> whose operator’s name
starts with “DB”; this is the station’s meta node. We can filter on tags
with either <code>=</code> for simple equality, or <code>~</code> to match against a regular
expression.</li>
<li>Then “walk up” the graph that node’s membership relations by one step (<code class="sourceCode overpassql"><span class="dt">&lt;</span></code>)
to find the station’s meta-relation, which is tagged with
<code class="sourceCode overpassql"><span class="st">&quot;public_transport&quot;</span><span class="ot">=</span><span class="st">&quot;stop_area&quot;</span></code>.</li>
<li>“Walk down” membership relations (<code>&gt;&gt;</code>), and select everything that is tagged
as a platform anywhere below this relation.</li>
</ol>
<p>By default, the data “flows” implicitly from one line into the next: each statement
assigns its output to the “default variable” <code class="sourceCode overpassql"><span class="dt">._</span></code>, and the next
statement reads from it. We could also write these explicitely and get a query
with the same semantics, like this:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode overpassql"><code class="sourceCode overpassql"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[out:json][timeout:25]</span><span class="op">;</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="dt">node</span><span class="kw">[</span><span class="st">&quot;railway:ref&quot;</span><span class="kw">=</span><span class="st">&quot;MH&quot;</span><span class="kw">][</span><span class="st">&quot;operator&quot;</span><span class="kw">~</span><span class="st">&quot;^DB&quot;</span><span class="kw">]</span><span class="dt"> -&gt; ._</span><span class="op">;</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="dt">rel</span><span class="kw">[</span><span class="st">&quot;public_transport&quot;</span><span class="kw">=</span><span class="st">&quot;stop_area&quot;</span><span class="kw">](</span><span class="dt">&lt;._</span><span class="kw">)</span><span class="dt"> -&gt; ._</span><span class="op">;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="dt">nwr</span><span class="kw">[</span><span class="st">&quot;railway&quot;</span><span class="kw">=</span><span class="st">&quot;platform&quot;</span><span class="kw">](</span><span class="dt">&gt;&gt;._</span><span class="kw">)</span><span class="dt"> -&gt; ._</span><span class="op">;</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="dt">out geom</span><span class="op">;</span></span></code></pre></div>
<p>In a bit, we’ll see more complex queries use custom variables.</p>
<p>For running test queries like this, it’s best to use <a href="https://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A25%5D%3B%0Anode%5B%22railway%3Aref%22%3D%22BLS%22%5D%5Boperator%7E%22%5EDB+%22%5D%3B%0Arelation%5Bpublic_transport%3Dstop_area%5D%28%3C%29%3B%0Anwr%5B%22railway%22%3D%22platform%22%5D%28%3E%3E%29%3B%0Aout+geom%3B&amp;C=52.524528%3B13.368713%3B17&amp;R=">overpass turbo</a>.
Note that queries which don’t return any geographical features won’t show up
on the map; if your query seems to return an empty result, switch to the <code>data</code>
tab on the top right to see its result. On the other hand, should a query result
in an error, that is always shown.</p>
<p>For now, our query does not even find everything in München Hbf’s upper floor: both wing
stations along with the underground S-Bahn station, are missing.</p>
<p>Wing stations are modelled in one of two ways: there is the <code>railway:ref:parent</code>
tag used for for stations which have a clear hierarchy between them: KKDT
‘belongs’ to KKDZ, MH N and MH S ‘belong’ to MH, etc.</p>
<p>Let’s incorporate that:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode overpassql"><code class="sourceCode overpassql"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[out:json][timeout:25]</span><span class="op">;</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="dt">node</span><span class="kw">[~</span><span class="st">&quot;railway:ref|railway:ref:parent&quot;</span><span class="kw">~</span><span class="st">&quot;^MH$&quot;</span><span class="kw">][</span><span class="st">&quot;operator&quot;</span><span class="kw">~</span><span class="st">&quot;^DB &quot;</span><span class="kw">]</span><span class="op">;</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="dt">relation</span><span class="kw">[</span><span class="st">&quot;public_transport&quot;</span><span class="kw">=</span><span class="st">&quot;stop_area&quot;</span><span class="kw">](</span><span class="dt">&lt;</span><span class="kw">)</span><span class="op">;</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="dt">nwr</span><span class="kw">[</span><span class="st">&quot;railway&quot;</span><span class="kw">=</span><span class="st">&quot;platform&quot;</span><span class="kw">](</span><span class="dt">&gt;&gt;</span><span class="kw">)</span><span class="op">;</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="dt">out geom</span><span class="op">;</span></span></code></pre></div>
<p>This does almost the same as before, but in the first statement the simple
filter for equality of a tag has been replaced with one which matches tags
against a regular expression, marked as such by having a <code>~</code> in front.</p>
<p>This now <a href="https://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A25%5D%3B%0Anode%5B%7E%22railway%3Aref%7Crailway%3Aref%3Aparent%22%7E%22%5EMH%24%22%5D%5Boperator%7E%22%5EDB+%22%5D%3B%0Arelation%5Bpublic_transport%3Dstop_area%5D%28%3C%29%3B%0Anwr%5B%22railway%22%3D%22platform%22%5D%28%3E%3E%29%3B%0Aout+geom%3B&amp;C=48.140561%3B11.554511%3B17&amp;R=#">catches both wing stations</a>.
But the S-Bahn is still missing.</p>
<p>To handle large, grouped stations, we need to more fully traverse the
tree:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode overpassql"><code class="sourceCode overpassql"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[out:json][timeout:25]</span><span class="op">;</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="dt">node</span><span class="kw">[~</span><span class="st">&quot;railway:ref|railway:ref:parent&quot;</span><span class="kw">~</span><span class="st">&quot;^MH$&quot;</span><span class="kw">][</span><span class="st">&quot;operator&quot;</span><span class="kw">~</span><span class="st">&quot;^DB &quot;</span><span class="kw">]</span><span class="op">;</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="dt">relation</span><span class="kw">[</span><span class="st">&quot;public_transport&quot;</span><span class="kw">~</span><span class="st">&quot;stop_area|stop_area_group&quot;</span><span class="kw">](</span><span class="dt">&lt;&lt;</span><span class="kw">)</span><span class="op">;</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="dt">nwr</span><span class="kw">[</span><span class="st">&quot;railway&quot;</span><span class="kw">=</span><span class="st">&quot;platform&quot;</span><span class="kw">](</span><span class="dt">&gt;&gt;</span><span class="kw">)</span><span class="op">;</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="dt">out geom</span><span class="op">;</span></span></code></pre></div>
<p>The above now uses the <code>&lt;&lt;</code> operator, which gives the transitive closure of
membership relations upwards. This does indeed now find everything we wanted –
but using it is expensive: recursively walking upwards winds up traversing through
a lot of things we aren’t at all interested in. Unfortunately, there seems to
be no way to give a limit to that operation, nor can it filter out things as
it goes along: it first traverses everything, and only then starts applying the
filter.</p>
<p>This is bad. The query now takes several (5-15) seconds, even for relatively
small stations.</p>
<p>Instead, we can use the <code class="sourceCode overpassql"><span class="dt">rel</span><span class="kw">(</span><span class="dt">bn</span><span class="kw">)</span></code> and <code class="sourceCode overpassql"><span class="dt">rel</span><span class="kw">(</span><span class="dt">br</span><span class="kw">)</span></code>
functions, which at least limit use to nodes and relations “on the go”, so we
won’t needlessly walk along ways (in our case, ‘ways’ have an unfortunate tendency
to model entire railway lines, greatly adding to needlessly-traversed data).</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode overpassql"><code class="sourceCode overpassql"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[out:json][timeout:25]</span><span class="op">;</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="dt">node</span><span class="kw">[~</span><span class="st">&quot;railway:ref|railway:ref:parent&quot;</span><span class="kw">~</span><span class="st">&quot;^MH$&quot;</span><span class="kw">][</span><span class="st">&quot;operator&quot;</span><span class="kw">~</span><span class="st">&quot;^DB &quot;</span><span class="kw">]</span><span class="op">;</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="dt">rel</span><span class="kw">[</span><span class="st">&quot;public_transport&quot;</span><span class="kw">~</span><span class="st">&quot;stop_area|stop_area_group&quot;</span><span class="kw">](</span><span class="dt">bn</span><span class="kw">)</span><span class="dt"> -&gt; .a</span><span class="op">;</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="dt">rel</span><span class="kw">[</span><span class="st">&quot;public_transport&quot;</span><span class="kw">~</span><span class="st">&quot;stop_area|stop_area_group&quot;</span><span class="kw">](</span><span class="dt">br.a</span><span class="kw">)</span><span class="dt"> -&gt; .b</span><span class="op">;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="kw">(</span><span class="dt">.a</span><span class="op">;</span><span class="dt">.b</span><span class="op">;</span><span class="kw">)</span><span class="op">;</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="dt">nwr</span><span class="kw">[</span><span class="st">&quot;railway&quot;</span><span class="kw">=</span><span class="st">&quot;platform&quot;</span><span class="kw">](</span><span class="dt">&gt;&gt;</span><span class="kw">)</span><span class="op">;</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="dt">out geom</span><span class="op">;</span></span></code></pre></div>
<p>This still finds the same as above, but takes much less time to run.
It now also uses named variables: to get both nodes and relations above the
original meta node, we first walk up to nodes using <code class="sourceCode overpassql"><span class="dt">rel</span><span class="kw">(</span><span class="dt">bn</span><span class="kw">)</span></code> and
assign the result to the name <code class="sourceCode overpassql"><span class="dt">.a</span></code>, then walk up from that to relations using
<code class="sourceCode overpassql"><span class="dt">rel</span><span class="kw">(</span><span class="dt">br.a</span><span class="kw">)</span></code>, and give that the name <code class="sourceCode overpassql"><span class="dt">.b</span></code>.</p>
<p>The <code class="sourceCode overpassql"><span class="kw">(</span><span class="dt">.a</span><span class="op">;</span><span class="dt">.b</span><span class="op">;</span><span class="kw">)</span></code> clause then merges both sets of objects, and assigns it back to
the default variable <code class="sourceCode overpassql"><span class="dt">._</span></code> as normal, so the next statement
can use it as its implicit input.</p>
<h3 id="testing">Testing</h3>
<p>I deployed this version of the query to <a href="https://bahnhof.name">bahnhof.name</a>
almost <a href="https://pleroma.stuebinm.eu/notice/AbVJ5BUu9f2gllONv6">a month ago now</a>,
and since then have received many pointers to stations where it failed to give
any reasonable result.</p>
<p>In some cases this could be traced to things not being mapped in OSM at all —
but often, there was something I had simply missed:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode overpassql"><code class="sourceCode overpassql"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[out:json][timeout:25]</span><span class="op">;</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="dt">nwr</span><span class="kw">[~</span><span class="st">&quot;railway:ref|railway:ref:parent&quot;</span><span class="kw">~</span><span class="st">&quot;^MH$&quot;</span><span class="kw">][operator~</span><span class="st">&quot;^(DB|Deutsch)&quot;</span><span class="kw">]</span><span class="op">;</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="kw">(</span><span class="dt">._</span><span class="op">;</span><span class="dt">rel</span><span class="kw">[</span><span class="st">&quot;public_transport&quot;</span><span class="kw">~</span><span class="st">&quot;stop_area|stop_area_group&quot;</span><span class="kw">](</span><span class="dt">bn</span><span class="kw">)</span><span class="op">;</span><span class="kw">)</span><span class="dt"> -&gt; .a</span><span class="op">;</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="dt">rel</span><span class="kw">[</span><span class="st">&quot;public_transport&quot;</span><span class="kw">~</span><span class="st">&quot;stop_area|stop_area_group&quot;</span><span class="kw">](</span><span class="dt">br.a</span><span class="kw">)</span><span class="dt"> -&gt; .b</span><span class="op">;</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="kw">(</span><span class="dt">.a</span><span class="op">;</span><span class="dt">.b</span><span class="op">;</span><span class="kw">)</span><span class="op">;</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="dt">nwr</span><span class="kw">[railway=platform](</span><span class="dt">&gt;&gt;</span><span class="kw">)</span><span class="op">;</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a><span class="dt">out geom</span><span class="op">;</span></span></code></pre></div>
<p>Two major changes: one, there’s now an additional merge operation to keep the
original meta node when using <code class="sourceCode overpassql"><span class="dt">rel</span><span class="kw">(</span><span class="dt">bn</span><span class="kw">)</span></code>; otherwise some information
gets lost in a few cases.</p>
<p>The second change is perhaps more significant: the match against the <code>operator</code>
tag has become more complex — because although all passenger stations “run by Deutsche Bahn”
are operated by DB Netz AG on the railway-infrastructure side and by DB Station &amp;
Service on the passenger side, there is no consensus <em>at all</em> how to refer to this
situation in OSM. The <code>operator</code> tag thus might contain variants of “DB Netze”
or “DB Station &amp; Service”, or merely “DB” or “Deutsche Bahn”, or anything else
vaguely along these lines.</p>
<p>Since we use the <code>railway:ref</code> tag to identify stations, and these identifiers
are specific to Deutsche Bahn, there isn’t really much we can do about this
situation other than attempting to match as many variants as we can.</p>
<h2 id="catch-them-all">Catch them all?</h2>
<p>It would be good to have some measure of certainty about how well this query
works, as an attempt to measure its usefulness. What percentage of stations
actually have platforms mapped and are found by our query?</p>
<h3 id="names-are-hard">Names are hard</h3>
<div class="side-note">
<p><span class="header">railway:ref</span></p>
<p>This tag <a href="https://wiki.openstreetmap.org/wiki/Key:railway:ref?uselang=en">contains the “internal abbre­viation”</a>
used by a station’s operator; essentially, an ID for this station. As an example,
referring to Berlin's main station by <code>BL</code> is less ambiguous than “Berlin Hbf” or
“Berlin-Haupt­bahn­hof” or even the inexplicably still existing name “Berlin
Lehrter Bahn­hof”</p>
</div>
<p>So far, I’ve avoided talking about how we find the stations in the first place:
via their Ril100 code, which in OSM is contained in the <code>railway:ref</code> tag.
Initially I wrote the queries to work with these because it was
convenient — I am familiar with the codes for stations I visit often, and they avoid
dealing with the fuzzier problems of station names.</p>
<div class="side-note">
<p><span class="header">Better IDs</span></p>
<p>There are better, more unique ways to address individual stations, such as
<a href="https://www.transmodel-cen.eu/">Transmodel</a>’s IFOPT standard, which gives IDs
to every public transit stop (not just railway stations) in the EU <span class="citation" data-cites="IFOPT VDV432">[<a href="#ref-IFOPT" role="doc-biblioref">EN28701</a>; <a href="#ref-VDV432" role="doc-biblioref">VDV432</a>]</span>,
or UIC numbers, which give numbers to every station in Eurasia and northern Africa.</p>
<p>But these are less consistently tagged across OSM, so I decided not to rely on
them.</p>
</div>
<p>Unfortunately, these codes have a major disadvantage: they are operator-specific,
in our case to Deutsche Bahn. Within Germany, this seldom matters — almost all
German station either are or historically were operated by Deutsche Bahn, and
thus have their own unique Ril100 code.</p>
<p>But OSM is a global project — and figuring out if what is contained in <code>railway:ref</code>
is a Ril100 code or something specific to another operator isn’t trivial; limiting
the query to stations run by Deutsche Bahn is only a (bad) approximation. Nor
can we assume that <code>railway:ref</code> is a Ril100 code for every station in Germany,
and for none outside it — for complicated reasons, national rail operators
sometimes operate stations outside their ‘home’ country; DB’s best-known example
of these is probably Basel Badischer Bahnhof, which is in Switzerland.</p>
<p>Annoyingly, this means that all other stations anywhere else in the world
are now automatically out of scope – and even the (rare) stations within Germany
not operated by some branch of DB are missing.</p>
<h3 id="so-many-betriebsstellen">So many Betriebsstellen</h3>
<p>Even then, attempting to get a hold of the actual <em>stations</em> can be a little
fuzzy. There is a complete list of all Ril100 codes <span class="citation" data-cites="Betriebsstellen">[<a href="#ref-Betriebsstellen" role="doc-biblioref">DBNetz21</a>]</span>, but it
does not match the usual intuitive meaning of “(passenger) railway stations”.</p>
<div class="side-note">
<p><span class="header">Betriebsstellen</span></p>
<p>Ril100 codes are primarily meant to designate <em>Betriebsstellen</em>, which for our pur­poses are
an almost comically broad category – meant to describe the railway from the
operator’s point of view <span class="citation" data-cites="ril408">[defined in <a href="#ref-ril408" role="doc-biblioref">Ril408</a>]</span>, and are usually not
exposed to the public at all.</p>
<p>A basic intuition for what has its own code is “thing which is in some way important
for the railway’s operation outside of its immediate surroundings”. Thus, stations
have a code, but <em>generally</em> not individual points within a station (but exceptions
exist). Many other things have codes, too: signal boxes, crossovers,
depots, electrical substations, repair shops, and even non-physical things like
national borders. <span class="citation" data-cites="Betriebsstellen">[<a href="#ref-Betriebsstellen" role="doc-biblioref">DBNetz21</a>]</span> even includes one or two joke entries.</p>
<p>It just so happens they caught on in train nerd circles, as convenient and easily-remembered
shorthands of stations.</p>
</div>
<p>Luckily, DB Station &amp; Service also publishes a list of what they consider
“stations”, also using Ril100 codes to identify them <span class="citation" data-cites="Stationsdaten">[<a href="#ref-Stationsdaten" role="doc-biblioref">DBStus20b</a>]</span>. This list
comes much closer to what we want. But it’s important to keep in mind that it’s
still not a complete match — though uncommon, there are still many stations or
stopping points in Germany which are not operated by DB Station &amp; Service, which
are not included here. Especially smaller, regional stations are thus
underrepresented.</p>
<h3 id="some-results">Some Results</h3>
<p>For the 5392 Ril100 codes contained in <span class="citation" data-cites="Stationsdaten">[<a href="#ref-Stationsdaten" role="doc-biblioref">DBStus20b</a>]</span>, the query returned a
non-empty result containing at least one platform for 3551 stations, of which
3071 contained at least one <code>ref</code> or <code>local_ref</code> tag. So at least we got over
half of them — but there’s still a large gap.</p>
<p>It is, of course, somewhat hard to judge what happened with the 1841 stations
for which nothing is found at all. At a glance, these skew heavily towards smaller
stations — but I’ve not yet had time to go through at least some of them manually,
and check whether they are simply not mapped at all in OSM, or mapped in some
unexpected way which the query could not find.</p>
<p>More interesting are the 480 stations for which the query <em>did</em> find at least
one platform in OSM, but with no <code>ref</code> or <code>local_ref</code> tag. What sort of stations
are these?</p>
<p>Well, the majority (383 of them) have only a single platform, so the
entire question of which connections are cross-platform becomes rather trivial.</p>
<p>The remaining 97 are a haphazard mix of still very-small stations, which are
otherwise well-mapped but, for whatever reason, simply lack track numbers.
The largest two, with
four platforms each, are <a href="https://www.openstreetmap.org/relation/12652967">Herlasgrün</a>
and <a href="https://www.openstreetmap.org/relation/2503785">Großkorbetha</a>, both in Saxony.</p>
<p>Somewhat hilariously, DB Station &amp; Service’s official <a href="https://bahnhof.de">bahnhof.de</a>
website doesn’t have track numbers for these on their maps, either, perhaps
suggesting that (at least for smaller stations) they <em>do</em> source their data
entirely from OSM and do not have their own maps. On the other hand, we can
definitely rule out that these tracks simply lack numbers entirely: on their
accessability info, there is a list of tracks for both
<a href="https://www.bahnhof.de/herlasgruen/ausstattung-barrierefreiheit">Herlasgrün</a>
and <a href="https://www.bahnhof.de/grosskorbetha/ausstattung-barrierefreiheit">Großkorbetha</a>
– just without any information on which track is where.</p>
<h2 id="bahnhof.name">Bahnhof.name</h2>
<p>As mentioned, I happened to already have a small web service for quickly looking
up Ril100 codes at <a href="https://bahnhof.name">bahnhof.name</a>. At first I extended it
with platform data by simply adding a static data set containing all platform
data I could find in OSM — but it turns out you can’t simply publish a service
backed by OSM data without people finding &amp; fixing mistakes in the data it
displays. So soon enough, I got people asking, <a href="https://chaos.social/@networkexception/111359542542074854">“hey, how long will changes
take to show up?”</a></p>
<p>So it now does dynamic updates instead, and caches results for a week. For
no particularly well-explainable reason, I also decided this was a good
opportunity to rewrite the entire thing in Haskell (before that and for even
less-clear reasons, I’d originally written it in Gleam).</p>
<div class="side-note">
<p><span class="header">Gleam</span></p>
<p><a href="https://gleam.run">Gleam</a> is a typed language which compiles to Erlang.
Overall it feels like a fun mashup of Haskell98’s type system
with Rust’s syntax. However, it lacks type classes, and sometimes I
found its syntax slightly inconvenient.</p>
</div>
<p>As a result, you can now look up a station’s platforms via e.g.</p>
<p>→ <a href="https://bahnhof.name/MH/tracks">https://bahnhof.name/MH/tracks</a></p>
<p>for München Hauptbahnhof. If you find a mistake in OSM and decide to fix it,
first of all, great! You can use</p>
<p>→ <a href="https://bahnhof.name/MH/tracks">https://bahnhof.name/MH/fetch</a></p>
<p>to forcibly invalidate the cache and re-fetch platform data from OSM afterwards.</p>
<h2 id="possible-improvements">Possible improvements</h2>
<p>There are some exciting ways in which this could be extended:</p>
<p>For one thing, lifting the restriction on DB-run stations would be great. This
shouldn’t be <em>too</em> hard — if push comes to shove, one can always look up a
station by its name — but neither is it entirely trivial. As mentioned, there
are more universally applicable station ID standards (in the shape of IFOPT or
UIC numbers) — but so far, these are not as widely used in OSM.</p>
<p>Much more complicated (but <em>very</em> useful) would be an attempt to create a kind
of inside-station routing engine, akin to that which the SBB’s app already has.
As far as I’m aware, this is probably not possible with the data that is
(currently) contained in OSM. Perhaps it would be possible to integrate data from
the official NeTEx data set — but matching that against the OSM data looks like
a daunting task.</p>
<p>Finally, for now there is one thing missing entirely: information on
platform sections (in Germany, usually designated A through G, with fewer
sections on shorter platforms). These would be especially useful, as many
other passenger information systems will tell you in which platform section
your carriage will stop — but for now, these are seemingly not modelled in OSM at
all, and I don’t even know where I’d begin if I wanted to add them.</p>
<p>EDIT: the above is incorrect! Thanks to <a href="https://plural.cafe/@trisschen/111506023715830790">trissc̈hen</a>
for pointing out to me that the <code>railway:platform:section</code> tag exists, which I’d
overlooked before.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Go have fun, and hopefully worry slightly less about your tightly-planned travel
itinerary!</p>
<p>I might revisit this some other time, or implement some of the ideas
in the section above — but well, we’ll see, and for now I’ll make no promises.
In the meantime, if you have ideas or improvements, feel free to <a href="https://pleroma.stuebinm.eu/users/stuebinm">poke me</a>.
I’ll also gladly accept patches for <a href="https://stuebinm.eu/git/bahnhof.name">bahnhof.name’s source code</a>.</p>
<p>Many thanks to many wonderful friends who helped point out things to me on the
way, to <a href="https://moira.is/">Moira</a> for patiently answering my questions about
what kinds of Betriebstellen exist, to <a href="https://nwex.de/">networkException</a>
for all their suggestions and for getting me to do live updates, to
<a href="https://firefly.nu">FireFly</a> and <a href="https://eno.space">some friendly dragons</a>
for listening to all my ramblings about NeTEx, OSM, and obscure stations (and
asking helpful questions along the way),
to <a href="https://fosstodon.org/@utf8equalsX">Fynn</a>
and everyone else who pointed out mistakes in the initial query’s results, and
especially thanks to everyone who has contributed to the station data contained
in OSM!</p>
<h2 id="references">References</h2>
<!--  LocalWords:  liminal
 -->
<div id="refs" class="references csl-bib-body" data-entry-spacing="0" role="list">
<div id="ref-Betriebsstellen" class="csl-entry" role="listitem">
<div class="csl-left-margin">[DBNetz21] </div><div class="csl-right-inline">DB Netz AG, Betriebstellenverzeichnis. in: <em>Open-Data-Portal. Das Datenportal der Deutschen Bahn AG</em>. 2021-10. <a href="https://data.deutschebahn.com/dataset/data-betriebsstellen.html">https://data.deutschebahn.com/dataset/data-betriebsstellen.html</a></div>
</div>
<div id="ref-Bahnsteigdaten" class="csl-entry" role="listitem">
<div class="csl-left-margin">[DBStus20a] </div><div class="csl-right-inline">DB Station &amp; Service AG, Bahnsteigdaten. in: <em>Open-Data-Portal. Das Datenportal der Deutschen Bahn AG</em>. 2020-03. <a href="https://data.deutschebahn.com/dataset/data-bahnsteig.html">https://data.deutschebahn.com/dataset/data-bahnsteig.html</a></div>
</div>
<div id="ref-Stationsdaten" class="csl-entry" role="listitem">
<div class="csl-left-margin">[DBStus20b] </div><div class="csl-right-inline">DB Station &amp; Service AG, Stationsdaten. in: <em>Open-Data-Portal. Das Datenportal der Deutschen Bahn AG</em>. 2020-03. <a href="https://data.deutschebahn.com/dataset/data-stationsdaten.html">https://data.deutschebahn.com/dataset/data-stationsdaten.html</a></div>
</div>
<div id="ref-IFOPT" class="csl-entry" role="listitem">
<div class="csl-left-margin">[EN28701] </div><div class="csl-right-inline">CEN, EN 28701: Intelligent transport systems - Public transport - Identification of Fixed Objects in Public Transport (IFOPT). 2012-12-01. </div>
</div>
<div id="ref-eureg" class="csl-entry" role="listitem">
<div class="csl-left-margin">[EU17] </div><div class="csl-right-inline">European Commission, Directorate-General for Mobility and Transport, Commission Delegated Regulation (EU) 2017/1926. in: <em>EUR-Lex</em>. 2017-05-31. <a href="http://data.europa.eu/eli/reg_del/2017/1926/oj">http://data.europa.eu/eli/reg_del/2017/1926/oj</a></div>
</div>
<div id="ref-Hann" class="csl-entry" role="listitem">
<div class="csl-left-margin">[Hann20] </div><div class="csl-right-inline">Andreás Hann, Understanding Overpass, the API of OpenStreetMap. 2020-01-17. <a href="https://hann.io/articles/2020/understanding-overpass/">https://hann.io/articles/2020/understanding-overpass/</a></div>
</div>
<div id="ref-ril408" class="csl-entry" role="listitem">
<div class="csl-left-margin">[Ril408] </div><div class="csl-right-inline">DB Netze, Fahrdienstvorschrift; Richtlinie 408.0101A01: Züge fahren; Begriffe. 2017. <a href="https://fahrweg.dbnetze.com/resource/blob/9724186/c8666883584b9cd1d415d69423158a05/40801_a04_gesamt-data.pdf">https://fahrweg.dbnetze.com/resource/blob/9724186/c8666883584b9cd1d415d69423158a05/40801_a04_gesamt-data.pdf</a></div>
</div>
<div id="ref-VDV432" class="csl-entry" role="listitem">
<div class="csl-left-margin">[VDV432] </div><div class="csl-right-inline">Verband Deutscher Verkehrsunternehmen, VDV-Schrift 432: Identifikation von Haltestellen. Anwendung der Global ID in Deutschland. 2022-09. <a href="https://www.vdv.de/downloads/3855/432%20%20SDS/forced">https://www.vdv.de/downloads/3855/432%20%20SDS/forced</a></div>
</div>
</div>
]]></description>
    <pubDate>Wed, 29 Nov 2023 00:00:00 UT</pubDate>
    <guid>https://stuebinm.eu/posts/am-selben-bahnsteig-gegen%C3%BCber.html</guid>
    <dc:creator>terru</dc:creator>
</item>
<item>
    <title>Run yourself a local telephone network with Asterisk and NixOS</title>
    <link>https://stuebinm.eu/posts/local-asterisk-nix-deployment.html</link>
    <description><![CDATA[<div class="toc-box"><div class="toc">

<ul>
<li><a href="#why" id="toc-why">Why?</a></li>
<li><a href="#overview" id="toc-overview">Overview</a></li>
<li><a href="#a-telephone-server" id="toc-a-telephone-server">A telephone server</a>
<ul>
<li><a href="#whats-a-phone-call" id="toc-whats-a-phone-call">What’s a phone call?</a></li>
<li><a href="#pjsip" id="toc-pjsip">PJSIP</a></li>
<li><a href="#extensions" id="toc-extensions">Extensions</a></li>
</ul></li>
<li><a href="#snom-snom" id="toc-snom-snom">Snom Snom</a></li>
<li><a href="#duut-duut-duut" id="toc-duut-duut-duut">duut-duut-duut …?</a></li>
<li><a href="#thinkpads-make-great-servers" id="toc-thinkpads-make-great-servers">Thinkpads make great servers</a></li>
<li><a href="#future-work" id="toc-future-work">Future Work</a></li>
<li><a href="#conclusion" id="toc-conclusion">Conclusion</a></li>
</ul>
</div></div>
<p>This is just a short post explaining how the phone network used by the VOC at
<a href="https://media.ccc.de/c/jev22/Freir%C3%A4ume">22f3</a> worked.</p>
<p>Note: this post is provided “as is”, with no assurance of correctness of any kind.
Be aware that it was written by someone who, three weeks ago, didn’t know a thing
about how any of this stuff worked, and that most of it is the result of a single
late-night config-file hacking session. It’s meant to be notes for myself should
I need such a setup again as much as it’s meant to be an explanation for others.</p>
<h2 id="why">Why?</h2>
<p>Traditionally, there is a lot of DECT on Congress, run by
<a href="https://eventphone.de/blog/">eventphone / the POC</a> and used by all kinds of
beings for all sorts of things. But 22f3 was not Congress (nor was it trying to
be); there was no visitor-facing infrastructure for calling each other, and if
people in the event’s orga had to talk to someone not in the same room, they
mostly used old-school walkie-talkies, which also get the job done (but don’t
need any physical infrastructure at the location, and so could be used from
early stages of buildup right through the event to the end of teardown).</p>
<p>But seen from the VOC, walkie-talkies have a couple disadvantages:</p>
<ul>
<li>even if you turn off all their beeping, walkie-talkies are <em>noisy</em>, which is
not a good thing to be if you are trying to be quiet while recording a talk.</li>
<li>their sound quality tends to be bad, and especially when people try to talk
quietly, understanding each other is difficult</li>
<li>this maybe could’ve been mitigated by using earpieces, but we had none</li>
<li>they generally cause anxiety, especially if there’s neurodivergent people
around (hi!)</li>
</ul>
<p>Yet since a surprising room plan change the week before the event meant that the
lecture halls and backoffice were all pretty far apart, we needed some way of
contacting everything that was quicker than running up and down four flights
of stairs.</p>
<p>So, phones.</p>
<h2 id="overview">Overview</h2>
<p>The <a href="https://c3voc.de/wiki/hardware:room-case">standard c3voc room case</a> already
contains a snom IP telephone, so we had one for each of the lecture halls where
we had a recording setup. I also got two more cheaply (10€) from Ebay Klein­anzeigen.</p>
<p>The VOC’s main use case for these is that if something breaks in a lecture hall
and the people there don’t know how to fix it, they can call our backoffice for
help.</p>
<p>Less important but still nice to have is the reverse direction: if the backoffice
notices that something is off, it can also call the lecture halls. The ring tone
on the snoms can be set to a very quiet “ding”-sound — and while we never
actually had to call a lecture hall during a talk, it’s still nice to know
you <em>could</em> do so if necessary without causing further disturbance.</p>
<h2 id="a-telephone-server">A telephone server</h2>
<p>We used <a href="https://www.asterisk.org/">asterisk</a> running on a NixOS machine as an
SIP server to let the phones talk to each other.</p>
<p>Asterisk is — in both good and confusing — software that was conceived in the
90ies, so it expects lots of config files in a self-defined format under <code>/etc</code>,
and also while using it you can occasionally smell the C. It comes with
<a href="https://wiki.asterisk.org/wiki/display/AST/Getting+Started">exhaustive documentation</a>,
which is frequently helpful, but in some corners itself notes that it’s partially
incomplete, unwritten, or that some passages have probably fallen out of date.</p>
<p>Luckily, NixOS comes with a corresponding module, and the following is
enough to start it:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="op">{</span> <span class="va">config</span><span class="op">,</span> <span class="va">lib</span><span class="op">,</span> <span class="va">pkgs</span><span class="op">,</span> <span class="op">...</span> <span class="op">}</span>:</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>  <span class="va">services</span>.<span class="va">asterisk</span> <span class="op">=</span> <span class="op">{</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    <span class="va">enable</span> <span class="op">=</span> <span class="cn">true</span><span class="op">;</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    <span class="va">confFiles</span> <span class="op">=</span> <span class="op">{</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>      <span class="co"># config files go here</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>    <span class="op">};</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>  <span class="op">};</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>  <span class="co"># we had a sepearte VLAN for this, so *shrug*</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>  <span class="co"># makes things easier if I don&#39;t have to keep track of ports</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>  <span class="va">networking</span>.<span class="va">firewall</span>.<span class="va">enable</span> <span class="op">=</span> <span class="cn">false</span><span class="op">;</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>If you add the snippet above to your configuration and switch to it asterisk
won’t do much yet, but it also won’t fail on startup <a href="https://search.nixos.org/options?channel=22.11&amp;show=services.asterisk.useTheseDefaultConfFiles&amp;from=0&amp;size=50&amp;sort=relevance&amp;type=packages&amp;query=asterisk">because NixOS gives it a
couple default config files</a>.
It will still try to load a lot of unconfigured modules which fail immediately,
but these errors are non-fatal and can be safely ignored.</p>
<p>One thing to note: Asterisk can live-reload its own config, and to
avoid breaking things NixOS won’t automatically restart asterisk when doing
<code>nixos-rebuild switch</code>. This is very sensible, but it does also mean you shouldn’t
forget to manually restart asterisk or make it reload its config before wondering
why a deploy didn’t do anything.</p>
<h3 id="whats-a-phone-call">What’s a phone call?</h3>
<p>Asterisk is an amazingly powerful piece of software, and can apparently do
everything from interacting with actual, analog telephone hardware to shepherding
WebRTC sessions. Unfortunately this generality also means it has no simple,
high-level concept of what a “phone call” is.</p>
<p>It <em>does</em> know things called “channels” and “bridges”. I recommend reading the
<a href="https://wiki.asterisk.org/wiki/display/AST/Asterisk+Architecture">Asterisk Architecture</a>
section of the “Getting Started”-Guide before attempting to do anything with it.</p>
<h3 id="pjsip">PJSIP</h3>
<p>Asterisk has (at least) two ways of interacting with SIP: the older
<a href="https://wiki.asterisk.org/wiki/display/AST/Configuring+chan_sip">chan_sip</a> module,
and the newer <a href="https://wiki.asterisk.org/wiki/display/AST/Configuring+res_pjsip">res_pjsip</a>.
The latter is recommended, but although the former is deprecated and will be
removed in a future version, often the wiki’s examples still uses the former and
only informally note what would change with PJSIP.</p>
<p>So here’s an annotated <code>pjsip.conf</code> example, patched together from the wiki:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode asterisk"><code class="sourceCode asterisk"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co">; we use UDP for transport</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="kw">[transport-udp]</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="dt">type</span><span class="ot">=</span><span class="st">transport</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="dt">protocol</span><span class="ot">=</span><span class="st">udp</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="dt">bind</span><span class="ot">=</span><span class="st">0.0.0.0</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="co">; Note: this defines a macro, to shorten the config further down</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="kw">[endpoint_internal](!)</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="dt">type</span><span class="ot">=</span><span class="st">endpoint</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a><span class="dt">context</span><span class="ot">=</span><span class="st">from-internal</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a><span class="dt">disallow</span><span class="ot">=</span><span class="st">all</span></span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a><span class="dt">allow</span><span class="ot">=</span><span class="st">ulaw</span></span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a><span class="kw">[auth_userpass](!)</span></span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a><span class="dt">type</span><span class="ot">=</span><span class="st">auth</span></span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a><span class="dt">auth_type</span><span class="ot">=</span><span class="st">userpass</span></span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a><span class="kw">[aor_dynamic](!)</span></span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a><span class="dt">type</span><span class="ot">=</span><span class="st">aor</span></span>
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true" tabindex="-1"></a><span class="dt">max_contacts</span><span class="ot">=</span><span class="st">1</span></span>
<span id="cb2-21"><a href="#cb2-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-22"><a href="#cb2-22" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-23"><a href="#cb2-23" aria-hidden="true" tabindex="-1"></a><span class="co">; here come the definitions for our phones, using the macros from above</span></span>
<span id="cb2-24"><a href="#cb2-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-25"><a href="#cb2-25" aria-hidden="true" tabindex="-1"></a><span class="co">; lecture hall 1</span></span>
<span id="cb2-26"><a href="#cb2-26" aria-hidden="true" tabindex="-1"></a><span class="kw">[saal1](endpoint_internal)</span></span>
<span id="cb2-27"><a href="#cb2-27" aria-hidden="true" tabindex="-1"></a><span class="dt">auth</span><span class="ot">=</span><span class="st">saal1</span></span>
<span id="cb2-28"><a href="#cb2-28" aria-hidden="true" tabindex="-1"></a><span class="dt">aors</span><span class="ot">=</span><span class="st">saal1</span></span>
<span id="cb2-29"><a href="#cb2-29" aria-hidden="true" tabindex="-1"></a><span class="kw">[saal1](auth_userpass)</span></span>
<span id="cb2-30"><a href="#cb2-30" aria-hidden="true" tabindex="-1"></a><span class="co">; well, maybe set a better password than this</span></span>
<span id="cb2-31"><a href="#cb2-31" aria-hidden="true" tabindex="-1"></a><span class="dt">password</span><span class="ot">=</span><span class="st">saal1</span></span>
<span id="cb2-32"><a href="#cb2-32" aria-hidden="true" tabindex="-1"></a><span class="dt">username</span><span class="ot">=</span><span class="st">saal1</span></span>
<span id="cb2-33"><a href="#cb2-33" aria-hidden="true" tabindex="-1"></a><span class="kw">[saal1](aor_dynamic)</span></span>
<span id="cb2-34"><a href="#cb2-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-35"><a href="#cb2-35" aria-hidden="true" tabindex="-1"></a><span class="co">; lecture hall 2</span></span>
<span id="cb2-36"><a href="#cb2-36" aria-hidden="true" tabindex="-1"></a><span class="kw">[saal2](endpoint_internal)</span></span>
<span id="cb2-37"><a href="#cb2-37" aria-hidden="true" tabindex="-1"></a><span class="dt">auth</span><span class="ot">=</span><span class="st">saal2</span></span>
<span id="cb2-38"><a href="#cb2-38" aria-hidden="true" tabindex="-1"></a><span class="dt">aors</span><span class="ot">=</span><span class="st">saal2</span></span>
<span id="cb2-39"><a href="#cb2-39" aria-hidden="true" tabindex="-1"></a><span class="kw">[saal2](auth_userpass)</span></span>
<span id="cb2-40"><a href="#cb2-40" aria-hidden="true" tabindex="-1"></a><span class="dt">password</span><span class="ot">=</span><span class="st">saal2</span></span>
<span id="cb2-41"><a href="#cb2-41" aria-hidden="true" tabindex="-1"></a><span class="dt">username</span><span class="ot">=</span><span class="st">saal2</span></span>
<span id="cb2-42"><a href="#cb2-42" aria-hidden="true" tabindex="-1"></a><span class="kw">[saal2](aor_dynamic)</span></span>
<span id="cb2-43"><a href="#cb2-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-44"><a href="#cb2-44" aria-hidden="true" tabindex="-1"></a><span class="kw">[backoffice](endpoint_internal)</span></span>
<span id="cb2-45"><a href="#cb2-45" aria-hidden="true" tabindex="-1"></a><span class="dt">auth</span><span class="ot">=</span><span class="st">backoffice</span></span>
<span id="cb2-46"><a href="#cb2-46" aria-hidden="true" tabindex="-1"></a><span class="dt">aors</span><span class="ot">=</span><span class="st">backoffice</span></span>
<span id="cb2-47"><a href="#cb2-47" aria-hidden="true" tabindex="-1"></a><span class="kw">[backoffice](auth_userpass)</span></span>
<span id="cb2-48"><a href="#cb2-48" aria-hidden="true" tabindex="-1"></a><span class="dt">password</span><span class="ot">=</span><span class="st">backoffice</span></span>
<span id="cb2-49"><a href="#cb2-49" aria-hidden="true" tabindex="-1"></a><span class="dt">username</span><span class="ot">=</span><span class="st">backoffice</span></span>
<span id="cb2-50"><a href="#cb2-50" aria-hidden="true" tabindex="-1"></a><span class="kw">[backoffice](aor_dynamic)</span></span></code></pre></div>
<p>Note that a single phone usually consists of (at least) three things:</p>
<dl>
<dt><code>endpoint</code>:</dt>
<dd>
defines the “SIP account” and references the other two.
</dd>
<dt><code>auth</code>:</dt>
<dd>
defines the authentication method, which here is just a password stored in
plaintext.
</dd>
<dt><code>aor</code>:</dt>
<dd>
<p>defines how the server ought to reach the phone. Without further config,
this is done dynamically—SIP clients register themselves when they start up,
and the server remembers their IP address. But we could also just set a static
IP here (useful if you can’t get a SIP client to register correctly — e.g.
I didn’t get linphone to work except with static addresses), or do any number
of other things.</p>
<p>A phone doesn’t have to have an aor associated with it — but without one, you
have a phone that can only place calls, not receive any.</p>
</dd>
</dl>
<h3 id="extensions">Extensions</h3>
<p>This is all nice and fine, but so far we’ve not seen any phone numbers! These
go into the <code>extensions.conf</code> config file, and define how asterisk should create
bridges between the channels that PJSIP provides:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode asterisk"><code class="sourceCode asterisk"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[from-internal]</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="co">; dial the lecture rooms &amp; backoffice</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="co">; the syntax is NUMBER,SEQUENCE,FUNCTION</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="co">; to call someone do Dial(MODULE/account, timeout)</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="dt">exten </span><span class="ot">=&gt;</span><span class="st"> 1001</span><span class="op">,</span><span class="st">1</span><span class="op">,</span><span class="st">Dial</span><span class="op">(</span><span class="cn">PJSIP</span><span class="st">/saal1</span><span class="op">,</span><span class="st">20</span>)</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="dt">exten </span><span class="ot">=&gt;</span><span class="st"> 1002</span><span class="op">,</span><span class="st">1</span><span class="op">,</span><span class="st">Dial</span><span class="op">(</span><span class="cn">PJSIP</span><span class="st">/saal2</span><span class="op">,</span><span class="st">20</span>)</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="dt">exten </span><span class="ot">=&gt;</span><span class="st"> 1600</span><span class="op">,</span><span class="st">1</span><span class="op">,</span><span class="st">Dial</span><span class="op">(</span><span class="cn">PJSIP</span><span class="st">/backoffice</span><span class="op">,</span><span class="st">20</span>)</span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="co">; Dial 100 for &quot;hello, world&quot;</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="co">; this is useful when configuring/debugging clients (snoms)</span></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="dt">exten </span><span class="ot">=&gt;</span><span class="st"> 100</span><span class="op">,</span><span class="st">1</span><span class="op">,</span><span class="st">Answer</span><span class="op">(</span>)</span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a><span class="cn">same</span><span class="dt">  </span><span class="ot">=&gt;</span><span class="st">     n</span><span class="op">,</span><span class="st">Wait</span><span class="op">(</span><span class="st">1</span>)</span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a><span class="cn">same</span><span class="dt">  </span><span class="ot">=&gt;</span><span class="st">     n</span><span class="op">,</span><span class="st">Playback</span><span class="op">(</span><span class="st">hello-world</span>)</span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a><span class="cn">same</span><span class="dt">  </span><span class="ot">=&gt;</span><span class="st">     n</span><span class="op">,</span><span class="st">Hangup</span><span class="op">(</span>)</span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a><span class="co">; note: &quot;n&quot; is a keyword meaning &quot;the last line&#39;s value, plus 1&quot;</span></span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a><span class="co">; &quot;same&quot; is a keyword referring to the last-defined extension</span></span></code></pre></div>
<p>That’s all we need, and now we can connect our phones!</p>
<h2 id="snom-snom">Snom Snom</h2>
<p>These snoms (we had one snom 300 and three snom 320) are suprisingly comfortable,
no-fuss devices (though if you’ve bought yours used, you might have to figure out
<a href="https://www.3cx.de/sip-phones/snom-auf-werkseinstellungen-zuruecksetzen/#h.j97yzuopypt9">how to reset</a>
them first — for some reason, their manual doesn’t mention this). They can also
run on PoE, which is especially useful if you’ve run out of power adapters.</p>
<p>On startup, they will display their own IP address (or, of unconfigured, ask
you if you want them to use DHCP).</p>
<p>After that it’s easiest to use the web interface to configure them. Set the
name, password &amp; server of an identity, and enable that identity. If you expect
to make calls across a NAT, also go to the NAT tab &amp; set it to send keepalive
packets every second or so.</p>
<figure>
<img src="../images/snom_webinterface.jpg" alt="The backoffice snom’s webinterface, configuring identity 1 (the snom 320 is limited to 12 of these, which it numbers sequentially). Note that the SIP account name occurs three times, once for the name which is shown on the snom’s display, and apparently twice SIP itself. The last two of these should probably be identical." />
<figcaption aria-hidden="true">The backoffice snom’s webinterface, configuring identity 1 (the snom 320 is
limited to 12 of these, which it numbers sequentially). Note that the SIP
account name occurs three times, once for the name which is shown on the snom’s
display, and apparently twice SIP itself. The last two of these should probably
be identical.</figcaption>
</figure>
<p>Make sure to disable any other SIP accounts that might be configured (e.g. from
previous events) and which you don’t need.</p>
<h2 id="duut-duut-duut">duut-duut-duut …?</h2>
<p>You can ask the server which clients it knows by letting it display the current
<code>aors</code> of PJSIP. It’s best to enter the asterisk cli for this:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode shell"><code class="sourceCode shellsession"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="op">❯</span><span class="st"> </span><span class="cn">sudo</span><span class="st"> asterisk -r</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="co">Asterisk 19.7.0, Copyright (C) 1999 - 2022, Sangoma Technologies Corporation and others.</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="co">Created by Mark Spencer &lt;markster@digium.com&gt;</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="co">Asterisk comes with ABSOLUTELY NO WARRANTY; type &#39;core show warranty&#39; for details.</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="co">This is free software, with components licensed under the GNU General Public</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a><span class="co">License version 2 and other licenses; you are welcome to redistribute it under</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="co">certain conditions. Type &#39;core show license&#39; for details.</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="co">=========================================================================</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="co">Connected to Asterisk 19.7.0 currently running on televoc (pid = 704)</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="kw">televoc*CLI&gt;</span><span class="st"> pjsip show aors</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a><span class="co">      Aor:  &lt;Aor..............................................&gt;  &lt;MaxContact&gt;</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a><span class="co">    Contact:  &lt;Aor/ContactUri............................&gt; &lt;Hash....&gt; &lt;Status&gt; &lt;RTT(ms)..&gt;</span></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a><span class="co">==========================================================================================</span></span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a><span class="co">      Aor:  saal1                                                1</span></span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a><span class="co">      Aor:  saal2                                                1</span></span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a><span class="co">      Aor:  backoffice                                           1</span></span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a><span class="co">    Contact:  backoffice/sip:backoffice@10.0.73.117:2048;li e3bba38c1f NonQual         nan</span></span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a><span class="co">Objects found: 3</span></span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a><span class="kw">televoc*CLI&gt;</span><span class="st"> </span></span></code></pre></div>
<p>If a client has connected, their “Contact” field will include their IP address;
in the above example, only the backoffice phone has registered successfully.
Note that this does not <em>necessarily</em> mean the other phones can’t place calls,
just that the server doesn’t know how to reach them if anyone else tries to call
their extension.</p>
<p>There are many more useful things you can do in the asterisk cli interface;
poking around it is definitely worth it. Just as an example, you can start ad-hoc
calls, like this,</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode shell"><code class="sourceCode shellsession"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">televoc*CLI&gt;</span><span class="st"> channel originate PJSIP/saal1 extension 100@from-internal</span></span></code></pre></div>
<p>after which the saal1 phone should ring.</p>
<p>The wiki gives <a href="https://wiki.asterisk.org/wiki/display/AST/Creating+and+Manipulating+Channels+from+the+CLI">a few basic examples</a>,
but since most asterisk’s functionality is provided by individual modules, the
actually useful examples are a little spread out. Most of the time it’s easier to
find interesting things in the cli using tab-completion, and then using the
<a href="https://wiki.asterisk.org/wiki/display/AST/CLI+Syntax+and+Help+Commands">built-in help function</a>
to find out what they do.</p>
<p>Conversly, with the above config you can test if a client can reach the server
by dialing 100 and checking that it answers with the “hello, world”. If you’ve
done it wrong, it’ll probably reject your call immediately, and the snom will
display an SIP status code and error message on its display. Some of these may be
familiar to you from HTTP, but probably not all — <a href="https://en.wikipedia.org/wiki/List_of_SIP_response_codes">wikipedia has a complete
list</a> if you need it.</p>
<p>If you dial and nothing at all happens (not even a “dialing” or “hold” sound
signal), then you’ve probably run into network issues. Perhaps there’s a NAT or
a firewall in the way somewhere, or the snom is attempting to reach an IP where
there’s no server that could answer?</p>
<p>Some recommendations:</p>
<ul>
<li>give all clients, and especially the server, static IP addresses, or make
their DHCP leases static. This isn’t strictly necessary, but it makes fixing
problems much easier</li>
<li>if things don’t work for no apparent reason and were fine earlier, try
rebooting the snom in question, or at least make it re-register (there’s
a button for that in its web gui)</li>
<li>send keepalive packets if there’s a NAT somewhere (once every couple seconds
seems to work mostly fine), or the server won’t be able to reach the phone if
someone attempts to call it. This will <em>hopefully</em> be enough; if not, there
are entire subsections of the wiki dedicated to various NAT-related issues</li>
</ul>
<h2 id="thinkpads-make-great-servers">Thinkpads make great servers</h2>
<p>This is a sidenote, but: the asterisk server ran on an old <a href="https://www.thinkwiki.org/wiki/Category:T430">T430</a>
that the VOC had lying around, and which we kept in our backoffice the entire
time. I can highly recommend doing this over running it on e.g. a single-board
computer, simply because if in doubt, it comes with a keyboard and monitor built
in, so you can always quickly jump into asterisk’s cli interface.</p>
<p>Just don’t forget to set it so it won’t go to sleep if someone closes its lid
(and perhaps set the “Boot on AC Attach” BIOS option).</p>
<h2 id="future-work">Future Work</h2>
<p>There’s lots of other stuff asterisk can do, like DECT, or a dial-out &amp; dial-in
to a larger phone network, automatically redirecting calls if no one picks up,
callgroups, …</p>
<p>Maybe I’ll look at some of those the next time we have a surprise telephone
setup at some place where there’s no POC.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Overall, this setup worked pretty well. The stationary nature of the phones was
seldom an issue, and the overall lack of DECT was less noticable than I’d feared.</p>
<p>We only made a few calls during the event, but they were a great help in
resolving issues that came up, and made the overall work of the VOC much less
stressful.</p>
<p>You might’ve also noticed that one of the snoms is still unaccounted for — I
wrote at the beginning that we had four, and then only mentioned three of them
in the config. Well, it turns out that eventphone <a href="https://eventphone.de/doku/sip_configuration_hints">also runs an SIP server</a>,
so we logged the last snom into theirs so that people on other chaos events
could call us.</p>
<p>Of course, that <em>does</em> mean there was technically no need to run our own SIP
server in the first place; we could’ve just used the eventphone server.</p>
<p>But consider this: it was fun doing so!</p>
]]></description>
    <pubDate>Thu, 05 Jan 2023 00:00:00 UT</pubDate>
    <guid>https://stuebinm.eu/posts/local-asterisk-nix-deployment.html</guid>
    <dc:creator>terru</dc:creator>
</item>
<item>
    <title>Hacking on Isabelle/ML</title>
    <link>https://stuebinm.eu/posts/isabelle-ml-hacking.html</link>
    <description><![CDATA[<div class="toc-box"><div class="toc">
Hacking on Isabelle/ML
<ul>
<li><a href="#resources" id="toc-resources">Resources</a></li>
<li><a href="#basic-isabelleml" id="toc-basic-isabelleml">Basic Isabelle/ML</a>
<ul>
<li><a href="#how-to-execute-code" id="toc-how-to-execute-code">How to execute code?</a></li>
<li><a href="#printing-things" id="toc-printing-things">Printing things</a></li>
<li><a href="#what-are-types" id="toc-what-are-types">What are types?</a></li>
<li><a href="#what-are-terms" id="toc-what-are-terms">What are terms?</a></li>
<li><a href="#what-are-sorts" id="toc-what-are-sorts">What are sorts?</a></li>
</ul></li>
<li><a href="#how-to-find-things" id="toc-how-to-find-things">How to find things</a>
<ul>
<li><a href="#interactive-exploring-of-ml-files" id="toc-interactive-exploring-of-ml-files">Interactive exploring of ML files</a></li>
<li><a href="#layout-of-ml-files" id="toc-layout-of-ml-files">Layout of ML files</a></li>
</ul></li>
</ul>
</div></div>
<p>This is less a post than a couple of notes to myself; but perhaps they might also
be helpful to others when starting out. I may extend or update it later.</p>
<h2 id="resources">Resources</h2>
<p><strong>General</strong>: along with the official <a href="https://isabelle.in.tum.de/">Isabelle Homepage</a> there is <a href="https://isabelle.systems/">isabelle.systems</a>,
collecting helpful links to other sites.</p>
<p><strong>Specific to Isabelle/ML</strong>:</p>
<ul>
<li>the <a href="https://nms.kcl.ac.uk/christian.urban/Cookbook/">Isabelle/ML Cookbook</a>, which while incomplete (with only sporadic updates)
is still the best beginner-friendly source to get started with many things</li>
<li>Burkhart Wolff’s <a href="https://www.lri.fr/~wolff/papers/other/TR_my_commented_isabelle.pdf">My Personal, Ecclectic Isabelle Programming Manual</a>, which
has much deeper information for a few topics</li>
</ul>
<h2 id="basic-isabelleml">Basic Isabelle/ML</h2>
<p>This is just a collection of helpful functions so I’ll have a place to look
them up once I inevitably forget about them again.</p>
<h3 id="how-to-execute-code">How to execute code?</h3>
<p>Useful antiquotations:</p>
<ul>
<li><code>ML‹code›</code> executes the ML code; the surrounding theory can be accessed
via antiquotations, especially <code>@{context}</code>. Some functions may
complain about a “missing local theory context”; use these either from
inside an Isabelle command or with <code>local_setup</code></li>
<li><code>local_setup‹code›</code> expects the code to have type
<code>Proof.context =&gt; Proof.context</code>, i.e. to modify the given theory context.
Useful for hacking on functions that should eventually be called from an
Isabelle command</li>
</ul>
<h3 id="printing-things">Printing things</h3>
<p>Printing any value that can reasonably be printed:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode sml"><code class="sourceCode sml"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>writeln (@{make_string} ...)</span></code></pre></div>
<p>Pretty-printing terms (color-coding of variables depends on context):</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode sml"><code class="sourceCode sml"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>Pretty.writeln (Syntax.pretty_term @{context} intr)</span></code></pre></div>
<h3 id="what-are-types">What are types?</h3>
<p>Types are a simple ADT:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode sml"><code class="sourceCode sml"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">datatype</span> typ =</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>  Type  <span class="kw">of</span> <span class="dt">string</span> * typ <span class="dt">list</span> |</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>  TFree <span class="kw">of</span> <span class="dt">string</span> * sort |</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>  TVar  <span class="kw">of</span> indexname * sort</span></code></pre></div>
<ul>
<li><code>Type (name, args)</code> is an instance of type <code>name</code> (the name is qualified,
e.g. <code>Nat.Nat</code> instead of just <code>Nat</code>). If this type is parameterised, then
its arguments go into <code>args</code></li>
<li><code>TFree (name, sort)</code> is a type variable (e.g. <code>'a</code>)</li>
<li><code>TVar ((name,index), sort)</code> are schematic variables which may occur e.g. in
theorems and can be instantiated. Values with the same <code>name</code> but different
<code>index</code> are not considered equal.</li>
</ul>
<p>Additionally, there’s also <code>Term.dummyT</code> (which is really <code>Type ("dummy", [])</code>).
This is used to leave the type unspecified. Terms using this type may lead
to errors if passed to functions which do not expect them (but usually they
are resolved during type inference).</p>
<p>Note that there is no extra builtin function type; <code>a ⇒ b</code> is represented
simply as <code>Type ("fun", [a,b])</code>.</p>
<h3 id="what-are-terms">What are terms?</h3>
<p>Terms are also simply an ADT implementing Isabelle’s take on the lambda
calculus:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode sml"><code class="sourceCode sml"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">datatype</span> term =</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>  Const <span class="kw">of</span> <span class="dt">string</span> * typ |</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>  Free <span class="kw">of</span> <span class="dt">string</span> * typ |</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>  Var <span class="kw">of</span> indexname * typ |</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>  Bound <span class="kw">of</span> <span class="dt">int</span> |</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>  Abs <span class="kw">of</span> <span class="dt">string</span> * typ * term |</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>  $ <span class="kw">of</span> term * term</span></code></pre></div>
<ul>
<li><code>Const (name, type)</code> are constants defined outside of the term (this may
include function symbols, quantifiers, concrete values like <code>true~/~false</code>,
datatype constructors, etc.)</li>
<li><code>Free (name, type)</code> are free variables (when pretty-printed in jedit, these
are printed in blue)</li>
<li><code>Var ((name,index),type)</code> are <em>schematic variables</em> that can be
instantiated, e.g. when they occur in a theorem (usually printed with
a leading question mark); values with the same <code>name</code> but different
<code>index</code> are not considered equal</li>
<li><code>Bound index</code> is a variable bound by a lambda in the same term, referred
to using a deBruijn-index (dangling indices lead to errors). Note that in
this case the variables type and name are still recorded, but as part of
the lambda abstraction</li>
<li><code>Abs (name,type,body)</code> is the expression λname : type. body</li>
<li><code>a $ b</code> is function application</li>
</ul>
<p>Truly a lot of ways to represent a variable!</p>
<p>Note that all names must always be qualified by theory name (antiquatations
do this automatically — don’t forget to insert theory names when replacing
one with a concrete term!).</p>
<h3 id="what-are-sorts">What are sorts?</h3>
<p>There is one important other detail: <em>sorts</em>. These give an extremely simple
meta-logic over types: each sort is just a list of strings, and “subsumes” all lists
which contain (at least) the same elements.</p>
<p>There is a “top” sort (the empty list), and apart from that sorts are used
to implement things like locales (e.g. in a term <code>a &lt; b</code>, the types of <code>a</code>, <code>b</code>
should have a sort that contains <code>"Orderings.org"</code>).</p>
<p>Most sorts are just <code>["HOL.Type"]</code>. There is a <code>\&lt;^sort&gt;</code> antiquotation
to write them more easily.</p>
<h2 id="how-to-find-things">How to find things</h2>
<p>To find a specific function (especially if it’s a basic, “obvious” function,
like some variation of a map/fold), the best way is often to use <code>ripgrep</code>
on Isabelle’s <code>src</code> directory with a likely name/type signature.</p>
<p>The best way to find theories/ML files is likewise with <code>fd</code>.</p>
<p>Use <code>bat</code> (or <code>isabat</code>) to browse these quickly; pass <code>-l sml</code> for files ending
in <code>.ML</code> to get correct syntax highlighting.</p>
<p>Basic SML functions may be found in <code>contrib/polyml-x.x/src/basis/*</code>; many
additional useful (general) functions are in <code>src/Pure/library.ML</code>.</p>
<h3 id="interactive-exploring-of-ml-files">Interactive exploring of ML files</h3>
<p>Isabelle/jedit can provide “jump to definition” and similar features for ML
(as it does by default for Isabelle theory files), but only does so for files
explicitly loaded by some theory.</p>
<p>So to get these features e.g. in <code>inductive.ML</code>, do this:</p>
<pre class="isabelle"><code>ML_file ‹~~/src/HOL/Tools/inductive.ML›
</code></pre>
<p>(the double tilde is Isabelle’s home directory, value of <code>$ISABELLE_HOME</code>)</p>
<p>Sometimes this leads to errors (e.g. command defined twice); these are safe
to ignore.</p>
<h3 id="layout-of-ml-files">Layout of ML files</h3>
<p>Most ML files in Pure/HOL define a main signature, and everything contained
therein is available qualified with that file’s name, but capitalised; e.g.
the <code>result</code> type defined in <code>HOL/Tools/inductive.ML</code> is available as
<code>Inductive.result</code>.</p>
<p>Sometimes if an ML file is not part of any theory that is in <code>Main</code>; in that
case, import the corresponding theory into your own, and that signature will
be available.</p>
]]></description>
    <pubDate>Sun, 22 May 2022 00:00:00 UT</pubDate>
    <guid>https://stuebinm.eu/posts/isabelle-ml-hacking.html</guid>
    <dc:creator>terru</dc:creator>
</item>
<item>
    <title>Are Nix Expressions Pacman-Complete?</title>
    <link>https://stuebinm.eu/posts/nix-tic-tac-toe-complete.html</link>
    <description><![CDATA[<div class="toc-box"><div class="toc">
Are Nix Expressions Pacman-Complete?
<ul>
<li><a href="#introduction" id="toc-introduction">Introduction</a></li>
<li><a href="#some-facts-about-nix" id="toc-some-facts-about-nix">Some Facts about Nix</a></li>
<li><a href="#loopings-and-undecidability" id="toc-loopings-and-undecidability">Loopings and Undecidability</a></li>
<li><a href="#basic-io" id="toc-basic-io">basic I/O</a></li>
<li><a href="#waiting-for-input" id="toc-waiting-for-input">Waiting for Input</a></li>
<li><a href="#maximal-sharing" id="toc-maximal-sharing">Maximal Sharing</a></li>
<li><a href="#chaining" id="toc-chaining">Chaining</a></li>
<li><a href="#an-io-monad" id="toc-an-io-monad">An IO Monad</a></li>
<li><a href="#notation" id="toc-notation">Notation</a></li>
<li><a href="#monad-laws" id="toc-monad-laws">Monad Laws</a></li>
<li><a href="#lets-play" id="toc-lets-play">Let’s play!</a></li>
<li><a href="#addendum" id="toc-addendum">Addendum</a></li>
</ul>
</div></div>
<p>aka: likely the worst ‘what is a monad?’-post it is possible to write.</p>
<p>tl;dr: at the very least, they’re tic tac toe-complete!</p>
<h2 id="introduction">Introduction</h2>
<p>A: I wonder if Nix is Turing-Complete?</p>
<p>B: The Nix Expression language? Sure it is: it has λ, and β-reduction,
what more would you need?</p>
<p>A: Well – I guess so. But can we use it for other things than just
describing how to build things?</p>
<p>B: Not sure. It’s <em>meant</em> to describe how to build things, not to support
any extra language features that could get in the way.</p>
<p>A: But Guix does perfectly fine just using Scheme to define packages, and
that’s a general-purpose language!</p>
<p>B: I guess so. Scheme does have things like I/O, and side-effects, and …
– people just don’t use those when defining derivations.</p>
<p>A: <em>Does</em> Nix really not have I/O? It can read files, which sounds like input,
and it can write files, which sounds a lot like output. It can even print
to the console!</p>
<p>B: But all input has to happen when evaluation starts, all at once, I think.</p>
<p>A: Is that a problem?</p>
<p>B: Well, it different from other languages, where you could first print
something (say, a question) then wait for more input (let’s hope it’s an
answer) and then after that print some more stuff, and so on.</p>
<p>A: And you don’t believe we can do that in Nix?</p>
<p>B: No, I don’t think so. Can we?</p>
<h2 id="some-facts-about-nix">Some Facts about Nix</h2>
<p>A: Here’s some facts about Nix (the language):</p>
<ol type="1">
<li><p>It can read any file, from any path which it can construct as a string.
Moreover, it can also <em>import</em> that file and evaluate it as an expression.</p></li>
<li><p>It can write files with any content it can construct, but not to arbitrary
locations – only to set paths in the Nix Store.</p></li>
</ol>
<p>And here’s some facts about Nix (the interpreter):</p>
<ol type="1">
<li><p>It cannot freeze the entire file system while it is busy evaluating
some expression, nor can it take a snapshot of every file when it starts.</p></li>
<li><p>Which means that we can force it to read files that were created only
<em>after</em> it started evaluating!</p></li>
</ol>
<p>I think that’s already anough, actually. Do you see it?</p>
<p>B: Hm … not sure yet. But I did notice that (1) and (2) imply that Nix can
interpret itself – there’s an <code>eval</code> function:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">pkgs</span> <span class="op">=</span> <span class="bu">import</span> &lt;nixpkgs&gt; <span class="op">{};</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>  eval = <span class="va">expression</span><span class="op">:</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    <span class="bu">import</span> <span class="op">(</span>pkgs.writeText <span class="st">&quot;eval&quot;</span> expression<span class="op">)</span></span></code></pre></div>
<p>A: Huh, I didn’t think of that. So we can just plug strings into <code>eval</code>
and it’ll interpret them as Nix expressions?</p>
<pre><code>nix-repl&gt; eval &quot;1&quot;
[1/0/1 built] building eval1

[1 built]
</code></pre>
<p>Uhm …</p>
<p>B: Looks like that’s not what <code>nix repl</code> was designed for. It <em>did</em>
work, it’s just glued the resulting <code>1</code> right into the middle of the build
messages.</p>
<p>A: It’s easier to see for derivations:</p>
<pre><code>nix-repl&gt; eval &quot;(import &lt;nixpkgs&gt; {}).firefox&quot;
[2 built]
«derivation /nix/store/xp2ycak6xn4zhryvdwjdakgz5xmapqdk-firefox-89.0.drv»
</code></pre>
<h2 id="loopings-and-undecidability">Loopings and Undecidability</h2>
<p>A: You gave me an idea, though: what happens if we evaluate this?</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="va">loop</span> <span class="op">=</span> <span class="va">a</span><span class="op">:</span> <span class="kw">let</span> <span class="va">file</span> <span class="op">=</span> eval <span class="op">(</span><span class="bu">toString</span> a<span class="op">);</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>              <span class="kw">in</span> <span class="kw">if</span> file == <span class="dv">1</span> <span class="kw">then</span> loop a <span class="kw">else</span> <span class="st">&quot;done&quot;</span><span class="op">;</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span> loop <span class="dv">1</span></span></code></pre></div>
<pre><code>[2/3 built] error: stack overflow (possible infinite recursion)
</code></pre>
<p>Huh. Looks like Nix doesn’t have tail-call optimisation. And doesn’t it usually
detect infinite recursion?</p>
<p>B: Why would need to have tail-call optimisation? And detecting infinite
recursion in general is a hard problem, you can’t expect it to always work!</p>
<p>A: Hm.</p>
<p>B: What?</p>
<p>A: <code>if file == 1 ... then loop a</code>. That’s always true. We could put a more
complicated condition there.</p>
<p>B: Like what?</p>
<p>A: Like, say, a Turing machine.</p>
<p>B: Why would you even – no, don’t –</p>
<p>A: – too late, already done it: <a href="https://stuebinm.eu/git/playground/tree/nix-turing/turingmachine.nix">turingmachine.nix</a></p>
<pre><code>&gt; nix-build turingmachine.nix
building &#39;/nix/store/nm5kf2ybl29dsbj4l1d9bg6assivm9a1-now-state-0-went-left.drv&#39;...
building &#39;/nix/store/fhim7mirjgp3rliajiij9bd60sfxps3w-now-state-1-went-right.drv&#39;...
building &#39;/nix/store/wm930rdavr3vsn1r09zzh2d71yjvbfi5-now-state-2-went-left.drv&#39;...
building &#39;/nix/store/5z5p51a8lgxcgvr176l6gdsb2v0pyxj4-now-state-3-went-left.drv&#39;...
building &#39;/nix/store/xk8lrv8zgbdz20yggz52dx31g0pr3698-now-state-3-went-right.drv&#39;...
building &#39;/nix/store/cr8yl0bxs0v8ypp6w2md7andmky0qi3f-now-state-0-went-right.drv&#39;...
building &#39;/nix/store/x8aj86xjwzi63wv1qgfg473s8lxrfyla-now-state-1-went-right.drv&#39;...
building &#39;/nix/store/bdwi16w3kxil65phad8w6zc1s08si8xd-now-state-0-went-left.drv&#39;...
building &#39;/nix/store/8472d4sl3l9h6h0m1nyis3v2d6jpavrh-now-state-1-went-left.drv&#39;...
building &#39;/nix/store/fh5al936sl8phzpf1a9ycn64kmggzxmr-now-state-0-went-left.drv&#39;...
building &#39;/nix/store/df3mzfxygv50l8p218yxf3f31zqclc4r-now-state-1-went-left.drv&#39;...
building &#39;/nix/store/5jzb29yjy3lldhdh535n24m7mdgi56c7-now-state-0-went-left.drv&#39;...
building &#39;/nix/store/pdc7c4ssslyf14h4cdn99pwayjp55dvk-now-state-1-went-right.drv&#39;...
building &#39;/nix/store/ylvfk029nv6njd9g2kjjrqqrpy990dgh-now-state-2-went-left.drv&#39;...
building &#39;/nix/store/l3qr8rhih8syw1afv3b6sx7z19a2817r-now-state-3-went-left.drv&#39;...
building &#39;/nix/store/pf7znba9ipck4qnacjh3mwxdyl65kpkx-now-state-3-went-right.drv&#39;...
building &#39;/nix/store/s1y3jw3q8jam18g64xpk5f4prqamx76j-now-state-0-went-right.drv&#39;...
building &#39;/nix/store/xigm9h6w2vgbyjx16bs1srv2fkmdx976-now-state-1-went-right.drv&#39;...
building &#39;/nix/store/4bm2f2aqf76w8dckplxc9vx2lm1x3ysc-now-state-2-went-left.drv&#39;...
...
</code></pre>
<p>B: Oh god. Well, on the other hand — if you still expect Nix to detect
whether or not that thing leads to infinite recursion, you’re literally
asking it to solve the halting problem.</p>
<h2 id="basic-io">basic I/O</h2>
<p>B: Anyways, can we please stop this and go back to the actual topic of this
post?</p>
<p>A: you mean, I/O in Nix?</p>
<p>B: <em>sigh</em> yes, I guess I do.</p>
<p>A: So, fact (4) says we can read files that were created after Nix already
started evaluating. Here’s a particularly boring example:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="bu">builtins</span>.readFile <span class="op">(</span>pkgs.writeFile <span class="st">&quot;nix&quot;</span> <span class="st">&quot;Lorem Ipsum&quot;</span><span class="op">)</span></span></code></pre></div>
<p>B: right – this is what’s called “import from derivation”, and it’s also
how things like <a href="https://github.com/nmattia/niv">niv</a> work: write files into the nix store (using a fetcher
like <code>fetchGit</code>), then import them into Nix.</p>
<p>A: Sure, but actually we can just import any path – we’re not limited to
just those that are within the nix store:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="bu">builtins</span>.readFile <span class="st">&quot;/tmp/hello-nix.txt&quot;</span></span></code></pre></div>
<p>If we evaluate that, and then <em>very quickly</em> create that file (or force Nix
to do some lengthy operation first), then Nix will read something
a user put somewhere, after the start of evaluation!</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode shell"><code class="sourceCode shellsession"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="kw">&gt;</span><span class="st"> nix-build -E &#39;builtins.readFile &quot;/tmp/hello-nix.txt&quot;&#39; \</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="co">    &amp; echo hello &gt; /tmp/hello-nix.txt</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="co">error: expression does not evaluate to a derivation (or a set or list of those)</span></span></code></pre></div>
<p>See? It complains that the result isn’t a derivation, but it <em>doesn’t</em>
complain about a missing file.</p>
<p>B: You sure that it <em>would</em> complain otherwise?</p>
<p>A: Sure. Nix is untyped, how would it know that <code>builtins.readFile</code>
<em>doesn’t</em> produce a derivation until it’s done evaluating it?</p>
<div class="sourceCode" id="cb10" data-org-language="sh"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;</span> nix-build <span class="ex">-E</span> <span class="st">&#39;builtins.readFile &quot;/tmp/does-not-exist&quot;&#39;</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="ex">error:</span> opening file <span class="st">&#39;/tmp/does-not-exist&#39;</span>: No such file or directory</span></code></pre></div>
<p>See?</p>
<p>B: Okay, so technically we can create files just when we start Nix. I’m not
sure how that is useful for anything, though.</p>
<h2 id="waiting-for-input">Waiting for Input</h2>
<p>A: Well, let’s make Nix wait a little:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">pause</span> <span class="op">=</span> <span class="va">idx</span><span class="op">:</span> pkgs.stdenv.mkDerivation <span class="op">{</span></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a>    <span class="va">name</span> <span class="op">=</span> <span class="st">&quot;sleep-</span><span class="sc">${</span>seed<span class="sc">}</span><span class="st">&quot;</span><span class="op">;</span></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a>    <span class="va">src</span> <span class="op">=</span> pkgs.hello<span class="op">;</span> <span class="co"># just some dummy input</span></span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a>    <span class="va">phases</span> <span class="op">=</span> <span class="op">[</span> <span class="st">&quot;buildPhase&quot;</span> <span class="op">];</span></span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a>    <span class="va">buildPhase</span> <span class="op">=</span> <span class="st">&#39;&#39;</span></span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a><span class="st">      # change idx if used more than once in the same file to wait every time</span></span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a><span class="st">      # </span><span class="sc">${</span><span class="bu">toString</span> idx<span class="sc">}</span></span>
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a><span class="st">      sleep 2</span></span>
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a><span class="st">      echo waiting 3 ...</span></span>
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a><span class="st">      sleep 2</span></span>
<span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a><span class="st">      echo waiting 2 ...</span></span>
<span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a><span class="st">      sleep 2</span></span>
<span id="cb11-14"><a href="#cb11-14" aria-hidden="true" tabindex="-1"></a><span class="st">      echo waiting 1 ...</span></span>
<span id="cb11-15"><a href="#cb11-15" aria-hidden="true" tabindex="-1"></a><span class="st">      # without this, Nix would consider evaluation to have failed</span></span>
<span id="cb11-16"><a href="#cb11-16" aria-hidden="true" tabindex="-1"></a><span class="st">      mkdir -p $out</span></span>
<span id="cb11-17"><a href="#cb11-17" aria-hidden="true" tabindex="-1"></a><span class="st">    &#39;&#39;</span><span class="op">;</span></span>
<span id="cb11-18"><a href="#cb11-18" aria-hidden="true" tabindex="-1"></a>  <span class="op">};</span></span>
<span id="cb11-19"><a href="#cb11-19" aria-hidden="true" tabindex="-1"></a>  <span class="va">pause_and</span> <span class="op">=</span> <span class="va">idx</span><span class="op">:</span> <span class="va">code</span><span class="op">:</span></span>
<span id="cb11-20"><a href="#cb11-20" aria-hidden="true" tabindex="-1"></a>    pkgs.stdenv.mkDerivation <span class="op">{</span></span>
<span id="cb11-21"><a href="#cb11-21" aria-hidden="true" tabindex="-1"></a>      <span class="va">name</span> <span class="op">=</span> <span class="st">&quot;pause_and</span><span class="sc">${</span><span class="bu">toString</span> idx<span class="sc">}</span><span class="st">&quot;</span><span class="op">;</span></span>
<span id="cb11-22"><a href="#cb11-22" aria-hidden="true" tabindex="-1"></a>      <span class="va">buildInputs</span> <span class="op">=</span> <span class="op">[</span> <span class="op">(</span>pause idx<span class="op">)</span> <span class="op">];</span></span>
<span id="cb11-23"><a href="#cb11-23" aria-hidden="true" tabindex="-1"></a>      <span class="va">phases</span> <span class="op">=</span> <span class="op">[</span> <span class="st">&quot;buildPhase&quot;</span> <span class="op">];</span></span>
<span id="cb11-24"><a href="#cb11-24" aria-hidden="true" tabindex="-1"></a>      <span class="va">src</span> <span class="op">=</span> pkgs.writeText <span class="st">&quot;code&quot;</span> code<span class="op">;</span></span>
<span id="cb11-25"><a href="#cb11-25" aria-hidden="true" tabindex="-1"></a>      <span class="va">buildPhase</span> <span class="op">=</span> <span class="st">&#39;&#39;</span></span>
<span id="cb11-26"><a href="#cb11-26" aria-hidden="true" tabindex="-1"></a><span class="st">        # </span><span class="sc">${</span>seed<span class="sc">}</span></span>
<span id="cb11-27"><a href="#cb11-27" aria-hidden="true" tabindex="-1"></a><span class="st">        cp $src $out</span></span>
<span id="cb11-28"><a href="#cb11-28" aria-hidden="true" tabindex="-1"></a><span class="st">      &#39;&#39;</span><span class="op">;</span></span>
<span id="cb11-29"><a href="#cb11-29" aria-hidden="true" tabindex="-1"></a>    <span class="op">};</span></span>
<span id="cb11-30"><a href="#cb11-30" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span> <span class="bu">import</span> <span class="op">(</span>pause_and <span class="dv">0</span> <span class="st">&quot;10&quot;</span><span class="op">)</span></span></code></pre></div>
<p>Using that, we can even write a handy little <code>read_input</code> function:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a>read_input = <span class="va">idx</span><span class="op">:</span> <span class="va">name</span><span class="op">:</span> <span class="bu">import</span> <span class="op">(</span>pause_and idx <span class="st">&#39;&#39;</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a><span class="st">  with import &lt;nixpkgs&gt; {};</span></span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a><span class="st">  # </span><span class="sc">${</span>seed<span class="sc">}</span></span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a><span class="st">  lib.readFile</span></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a><span class="st">    &quot;/tmp/input-</span><span class="sc">${</span>name<span class="sc">}</span><span class="st">&quot;</span></span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a><span class="st">&#39;&#39;</span><span class="op">)</span>;</span></code></pre></div>
<p>B: Why’s the <code>seed</code> variable in there? It doesn’t seem to be doing anything …</p>
<p>A: … except we can bind it to a different value each time we run the
build, making sure Nix will re-run everything even if nothing else has
changed and <code>idx</code> is the same.</p>
<p>Now we can use it like this:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a>  seed = <span class="bu">toString</span> <span class="dv">0</span>;</span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a>in</span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a>  read_input <span class="dv">0</span> <span class="st">&quot;test&quot;</span></span></code></pre></div>
<pre><code>&gt; nix-build &amp;
&gt; sleep 4 &amp;&amp; echo &quot;hello from the shell&quot; &gt; /tmp/input-test
[1] 21354
building &#39;/nix/store/07l20zp51ghl88d4xrpr8sw744fpi43r-sleep-0.drv&#39;...
building
waiting 3 ...
waiting 2 ...
waiting 1 ...
building &#39;/nix/store/8ibcbgbc4g60xan05xa49qcvncnaz35p-pause_and0.drv&#39;...
building
error: expression does not evaluate to a derivation (or a set or list of those)

[1]+  Exit 1                  nix-build scratch.nix
</code></pre>
<p>A: See?</p>
<h2 id="maximal-sharing">Maximal Sharing</h2>
<p>B: Quite. But what happens if you try reading in the same file twice?</p>
<p>A: uhm, well, let’s see …</p>
<div class="sourceCode" id="cb15"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="va">seed</span> <span class="op">=</span> <span class="bu">toString</span> <span class="dv">1</span><span class="op">;</span></span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span></span>
<span id="cb15-3"><a href="#cb15-3" aria-hidden="true" tabindex="-1"></a>  <span class="op">(</span>read_input <span class="dv">0</span> <span class="st">&quot;test&quot;</span><span class="op">)</span> <span class="op">+</span> <span class="op">(</span>read_input <span class="dv">0</span> <span class="st">&quot;test&quot;</span><span class="op">)</span></span></code></pre></div>
<pre><code>&gt; nix-build
building &#39;/nix/store/k56caykkwwc4dygxvnyq7dsrahz3j7bg-code.drv&#39;...
building &#39;/nix/store/ryh9f942r57ji220ccizscwypgdg8hbj-sleep-2.drv&#39;...
building
waiting 3 ...
waiting 2 ...
waiting 1 ...
building &#39;/nix/store/ldz27rinz91sws3zjlbq7grb42zmz6n7-pause_and1.drv&#39;...
building
error: expression does not evaluate to a derivation (or a set or list of those)
</code></pre>
<p>Huh. Looks like both <code>read_inputs</code> waited at the same time.</p>
<p>B: Not quite. Nix <a href="https://edolstra.github.io/pubs/phd-thesis.pdf#page=89">implements what it calls maximal lazyness</a>: if you do the
same thing twice, it’ll only evaluate it once.</p>
<p>A: Huh? But that was what the <code>idx</code> was for! … oh, I forgot to set it to
different values each time. My bad.</p>
<p>B: But even if you did set them to different values, which one would be
evaluated first?</p>
<p>A: Well, we can make one of them wait extra long:</p>
<div class="sourceCode" id="cb17"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="va">seed</span> <span class="op">=</span> <span class="bu">toString</span> <span class="dv">2</span><span class="op">;</span></span>
<span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span></span>
<span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a>  print</span>
<span id="cb17-4"><a href="#cb17-4" aria-hidden="true" tabindex="-1"></a>    <span class="op">((</span>read_input <span class="dv">0</span> <span class="st">&quot;test&quot;</span><span class="op">)</span></span>
<span id="cb17-5"><a href="#cb17-5" aria-hidden="true" tabindex="-1"></a>     <span class="op">+</span> <span class="op">(</span>pkgs.lib.readFile <span class="op">(</span>pause_and <span class="dv">2</span> <span class="op">(</span>read_input <span class="dv">1</span> <span class="st">&quot;test&quot;</span><span class="op">))))</span></span>
<span id="cb17-6"><a href="#cb17-6" aria-hidden="true" tabindex="-1"></a>    <span class="dv">4</span></span></code></pre></div>
<pre><code>&gt; nix-build
building &#39;/nix/store/wva39fi81zr0f3hlav0dz129zpvwcyvm-sleep-4.drv&#39;...
building
waiting 3 ...
waiting 2 ...
waiting 1 ...
building &#39;/nix/store/3l768hhidqxj4qz3wn4xak170gp69j0v-pause_and0.drv&#39;...
building
building &#39;/nix/store/90d3hggp881kapmlpsbqd506rsb9q4h4-sleep-4.drv&#39;...
building
waiting 3 ...
waiting 2 ...
waiting 1 ...
building &#39;/nix/store/gvly9wd9yjlk5bwvy01dhahs2cz8j40d-pause_and1.drv&#39;...
building
building &#39;/nix/store/4fzcwfrhdm3014515frv7mx7ki5wqjvd-sleep-4.drv&#39;...
building
waiting 3 ...
waiting 2 ...
waiting 1 ...
building &#39;/nix/store/s661xcgx66igj3qy4haf22k4xj0fqvi7-pause_and2.drv&#39;...
building
these derivations will be built:
  /nix/store/ki59fdjbihnza1zw7ygvp71ygwqlxp4d-print.drv
building &#39;/nix/store/ki59fdjbihnza1zw7ygvp71ygwqlxp4d-print.drv&#39;...
building
hello from the shell
hello from the shell

/nix/store/akxfzsq8wwi85pzidl8b4s7qi4q9p41w-print
</code></pre>
<p>B: … aaand it cached the file after the first read.</p>
<p>A: So no reading in the same file twice, I guess.</p>
<p>B: Maximal sharing can be annoying sometimes.</p>
<h2 id="chaining">Chaining</h2>
<p>A: I guess we could let <code>read_input</code> count how many times it’s been called
by passing some sort of state around, and then read from a different file
each time, like this:</p>
<div class="sourceCode" id="cb19"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span></span>
<span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">read_with_state</span> <span class="op">=</span> <span class="va">prompt</span><span class="op">:</span> <span class="va">oldstate</span><span class="op">:</span> <span class="op">{</span></span>
<span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a>    <span class="va">result</span> <span class="op">=</span> read_input oldstate.state <span class="op">(</span><span class="bu">toString</span> oldstate.state<span class="op">);</span></span>
<span id="cb19-4"><a href="#cb19-4" aria-hidden="true" tabindex="-1"></a>    <span class="va">state</span> <span class="op">=</span> oldstate.state <span class="op">+</span> <span class="dv">1</span><span class="op">;</span></span>
<span id="cb19-5"><a href="#cb19-5" aria-hidden="true" tabindex="-1"></a>  <span class="op">};</span></span>
<span id="cb19-6"><a href="#cb19-6" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span></span>
<span id="cb19-7"><a href="#cb19-7" aria-hidden="true" tabindex="-1"></a>  ...</span></code></pre></div>
<p>And then using it we just have to be careful to always pass that state from
one call to the next. It’s kinda like chaining them together, dragging a
sequential order into the evaluator’s laziness:</p>
<div class="sourceCode" id="cb20"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span></span>
<span id="cb20-2"><a href="#cb20-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">result1</span> <span class="op">=</span> read_with_state <span class="st">&quot;first&quot;</span> <span class="op">{</span><span class="va">state</span> <span class="op">=</span> <span class="dv">0</span><span class="op">;};</span></span>
<span id="cb20-3"><a href="#cb20-3" aria-hidden="true" tabindex="-1"></a>  <span class="va">first_input</span> <span class="op">=</span> result1.result<span class="op">;</span></span>
<span id="cb20-4"><a href="#cb20-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-5"><a href="#cb20-5" aria-hidden="true" tabindex="-1"></a>  <span class="va">result2</span> <span class="op">=</span> read_with_state <span class="st">&quot;second&quot;</span> result1<span class="op">;</span></span>
<span id="cb20-6"><a href="#cb20-6" aria-hidden="true" tabindex="-1"></a>  <span class="va">second_input</span> <span class="op">=</span> result2.result<span class="op">;</span></span>
<span id="cb20-7"><a href="#cb20-7" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span></span>
<span id="cb20-8"><a href="#cb20-8" aria-hidden="true" tabindex="-1"></a>  first_input <span class="op">+</span> second_input</span></code></pre></div>
<pre><code>building &#39;/nix/store/kqrc3nsxcm06f9924nxscvs8z2vsk52f-code.drv&#39;...
building &#39;/nix/store/qwhmkg3r6xmjzmf8yk5cp6z7gs1x0afw-sleep-5.drv&#39;...
building
waiting 3 ...
waiting 2 ...
waiting 1 ...
building &#39;/nix/store/48njzi1al7qgvrcyv4jy3ln5gg39jj1a-pause_and0.drv&#39;...
building
building &#39;/nix/store/qmvq75cls017pkq0bz1z5whxpyxqrv7q-code.drv&#39;...
building &#39;/nix/store/0gh8r26wjzciljxcnyq89bq5mj8ysll1-sleep-5.drv&#39;...
building
waiting 3 ...
waiting 2 ...
waiting 1 ...
building &#39;/nix/store/8jki0snrr21bh08pvy22afq3xgq3axp7-pause_and1.drv&#39;...
building
&quot;Hello from input 0\nHello from Input 1\n&quot;
</code></pre>
<p>B: You know, at this point you can probably stop pretending and just admit
that what you’re building is a monad.</p>
<h2 id="an-io-monad">An IO Monad</h2>
<p>A: A what?</p>
<p>B: That pattern of your <code>read_with_state</code> function – the way it always needs
to be called with some extra “state” value from the last time it was used,
wrapping its value into an extra structure that we don’t really care about
by itself? That pattern is what’s called a <em>monad</em>.</p>
<p>A: Huh.</p>
<p>B: See, we can define an operation that takes two of these functions and
chains them together (which is usually called <code>bind</code>). For yours, it would
look like this:</p>
<div class="sourceCode" id="cb22"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb22-1"><a href="#cb22-1" aria-hidden="true" tabindex="-1"></a>bind = <span class="va">monadic</span><span class="op">:</span> <span class="va">operation</span><span class="op">:</span></span>
<span id="cb22-2"><a href="#cb22-2" aria-hidden="true" tabindex="-1"></a>  monadic <span class="op">//</span> <span class="op">(</span>operation monadic.result<span class="op">)</span> monadic</span></code></pre></div>
<p>A: Hang on — what happened to the <code>state</code> attribute? It’s not mentioned
in your definition of <code>bind</code>.</p>
<p>B: Right, it isn’t — the assumption here is that <code>monadic</code> is one of these
attribute sets which has <code>result</code> and <code>state</code> attributes as above, and
<code>operation</code> is a function which takes a result, <em>and then also takes
the entire state</em>, and then returns another monadic value with the same
attributes. If we had types, we could say this function takes a value
of a certain type, a function, and then returns another value of the
same type.</p>
<p>A: But do we have types?</p>
<p>B: We don’t.</p>
<p>A: We don’t?</p>
<p>B: We don’t.</p>
<p>A: How annoying.</p>
<p>B: Yes. We’ll have to be very careful – if we do anything wrong, things
might just blow up and produce errors after half the evaluation!</p>
<p>A: Aaaaa! Why don’t we have types?</p>
<p>B: Because we don’t.</p>
<p>A: Exactly why?</p>
<p>B: Just because.</p>
<p>A: Okay, I guess we don’t. … anyways, where were we?</p>
<p>B: Explaining the <code>bind</code> function.</p>
<p>A: Ah, right. Why does <code>operation</code> take the <code>result</code> value as a first
argument? Seems like we could save an argument by simply passing it the
entire attribute set only once, since it already contains the <code>result</code>.</p>
<p>B: In theory, we could — but we’ll see in a moment why it’s more convenient
to do it this way.</p>
<p>A: Well, okay.</p>
<p>B: So now we can “bind” two reads together:</p>
<div class="sourceCode" id="cb23"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb23-1"><a href="#cb23-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span></span>
<span id="cb23-2"><a href="#cb23-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">double_read</span> <span class="op">=</span> bind</span>
<span id="cb23-3"><a href="#cb23-3" aria-hidden="true" tabindex="-1"></a>    <span class="op">(</span><span class="va">v</span><span class="op">:</span> read_with_state <span class="st">&quot;first&quot;</span><span class="op">)</span></span>
<span id="cb23-4"><a href="#cb23-4" aria-hidden="true" tabindex="-1"></a>    <span class="op">(</span><span class="va">v</span><span class="op">:</span> read_with_state <span class="st">&quot;second&quot;</span><span class="op">)</span></span>
<span id="cb23-5"><a href="#cb23-5" aria-hidden="true" tabindex="-1"></a>in</span>
<span id="cb23-6"><a href="#cb23-6" aria-hidden="true" tabindex="-1"></a>  double_read <span class="op">{</span><span class="va">idx</span> <span class="op">=</span> <span class="dv">0</span><span class="op">;}</span></span></code></pre></div>
<h2 id="notation">Notation</h2>
<p>A: What if we want to read in three things?</p>
<p>B: Yeah, uhm …</p>
<div class="sourceCode" id="cb24"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb24-1"><a href="#cb24-1" aria-hidden="true" tabindex="-1"></a>bind</span>
<span id="cb24-2"><a href="#cb24-2" aria-hidden="true" tabindex="-1"></a>  <span class="op">(</span><span class="va">v</span><span class="op">:</span> bind</span>
<span id="cb24-3"><a href="#cb24-3" aria-hidden="true" tabindex="-1"></a>    <span class="op">(</span><span class="va">v</span><span class="op">:</span> read_with_state <span class="st">&quot;first&quot;</span><span class="op">)</span></span>
<span id="cb24-4"><a href="#cb24-4" aria-hidden="true" tabindex="-1"></a>    <span class="op">(</span><span class="va">v</span><span class="op">:</span> read_with_state <span class="st">&quot;second&quot;</span><span class="op">))</span></span>
<span id="cb24-5"><a href="#cb24-5" aria-hidden="true" tabindex="-1"></a>  <span class="op">(</span><span class="va">v</span><span class="op">:</span> read_with_state <span class="st">&quot;third&quot;</span><span class="op">)</span></span>
<span id="cb24-6"><a href="#cb24-6" aria-hidden="true" tabindex="-1"></a>  <span class="op">{</span><span class="va">idx</span> <span class="op">=</span> <span class="dv">0</span><span class="op">;}</span></span></code></pre></div>
<p>A: Well that’s awkward.</p>
<p>B: We can do better, though:</p>
<div class="sourceCode" id="cb25"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb25-1"><a href="#cb25-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="va">do</span> <span class="op">=</span> <span class="va">operations</span><span class="op">:</span> <span class="va">monadic</span><span class="op">:</span></span>
<span id="cb25-2"><a href="#cb25-2" aria-hidden="true" tabindex="-1"></a>      pkgs.lib.foldl bind monadic operations<span class="op">;</span></span>
<span id="cb25-3"><a href="#cb25-3" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span></span>
<span id="cb25-4"><a href="#cb25-4" aria-hidden="true" tabindex="-1"></a>do <span class="op">[</span></span>
<span id="cb25-5"><a href="#cb25-5" aria-hidden="true" tabindex="-1"></a>  <span class="op">(</span><span class="va">v</span><span class="op">:</span> read_with_state <span class="st">&quot;first&quot;</span><span class="op">)</span></span>
<span id="cb25-6"><a href="#cb25-6" aria-hidden="true" tabindex="-1"></a>  <span class="op">(</span><span class="va">v</span><span class="op">:</span> read_with_state <span class="st">&quot;second&quot;</span><span class="op">)</span></span>
<span id="cb25-7"><a href="#cb25-7" aria-hidden="true" tabindex="-1"></a>  <span class="op">(</span><span class="va">v</span><span class="op">:</span> read_with_state <span class="st">&quot;third&quot;</span><span class="op">)</span></span>
<span id="cb25-8"><a href="#cb25-8" aria-hidden="true" tabindex="-1"></a><span class="op">]</span> <span class="op">{</span><span class="va">idx</span> <span class="op">=</span> <span class="dv">0</span><span class="op">;}</span></span></code></pre></div>
<p>A: Huh. Looks almost a little like an imperative language now.</p>
<p>B: I guess so.</p>
<h2 id="monad-laws">Monad Laws</h2>
<p>A: Hang on a minute. If I look up “monad” with a search engine, there’s all
this stuff about how it also needs some function called <code>pure</code>, and obey
“monad laws”, and …</p>
<p>B: Ah sorry, I skipped over these. Whoops.</p>
<p>A: So what do these do?</p>
<p>B: <code>pure</code> is very simple: it just takes a value, and returns it wrapped
into the monad, like this:</p>
<div class="sourceCode" id="cb26"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb26-1"><a href="#cb26-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="va">pure</span> <span class="op">=</span> <span class="va">val</span><span class="op">:</span> <span class="va">state</span><span class="op">:</span> state <span class="op">//</span> <span class="op">{</span><span class="va">value</span> <span class="op">=</span> val<span class="op">;}</span></span></code></pre></div>
<p>In fact, it’s often also called just that: <code>return</code>! (and sometimes also <code>unit</code>)</p>
<p>A: Why would we ever need to do that?</p>
<p>B: We can use it to change the value “inside” our monad – that is, change
the value that the next function gets:</p>
<div class="sourceCode" id="cb27"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb27-1"><a href="#cb27-1" aria-hidden="true" tabindex="-1"></a>do <span class="op">[</span></span>
<span id="cb27-2"><a href="#cb27-2" aria-hidden="true" tabindex="-1"></a>  <span class="op">(</span><span class="va">v</span><span class="op">:</span> read_with_state <span class="st">&quot;hello&quot;</span><span class="op">)</span></span>
<span id="cb27-3"><a href="#cb27-3" aria-hidden="true" tabindex="-1"></a>  <span class="op">(</span><span class="va">v</span><span class="op">:</span> pure <span class="op">(</span>v <span class="op">+</span> <span class="st">&quot;, world!&quot;</span><span class="op">))</span></span>
<span id="cb27-4"><a href="#cb27-4" aria-hidden="true" tabindex="-1"></a><span class="op">]</span></span></code></pre></div>
<pre><code>&gt; nix-build &amp;
&gt; echo &quot;hello&quot; &gt; /tmp/input-0
&quot;hello, world!&quot;
</code></pre>
<p>A: And the monad laws?</p>
<p>B: There’s three of them. Each is an equation of two expressions that
should evaluate to the same thing:</p>
<ol type="1">
<li>Left Identity: <code>bind (pure a) h</code> is the same as just <code>h a</code></li>
<li>Right Identity: <code>bind m pure</code> is the same as just <code>m</code></li>
<li>Associativity: <code>bind (bind m g) h</code> is the same as <code>bind m (v: bind (g v) h)</code></li>
</ol>
<p>A: What happens if one of these three doesn’t hold?</p>
<p>B: Weird things.</p>
<p>A: Such as?</p>
<p>B: Well, mostly things won’t behave as you may expect them to — they’re not
all that important here, but imagine we wanted an optimising compiler for
Nix: perhaps we could use these laws for optimisations?</p>
<p>A: Perhaps. How do we know the laws are in fact true, though, for our
definition of <code>bind</code>?</p>
<p>B: Well, if we had a formal semantics for Nix, we could write all the rules
down and then prove from those no matter what the variables in the laws are,
the laws will always hold.</p>
<p>A: Actually, we do have a formal semantics for Nix: it’s
<a href="https://edolstra.github.io/pubs/phd-thesis.pdf#page=79">in Eelco Dolstra’s Thesis</a>.</p>
<p>B: Looks out of date, though. Nix as described there only has the weird
old-style <code>let</code> — no wonder, it’s from 2006!</p>
<p>A: Yeah, and I can’t really find a newer version, either.</p>
<p>B: It sounds like a lot of work, anyways.</p>
<p>A: What could we do instead?</p>
<p>B: Squint at the functions for a bit, then shrug, move on, and hope that
it’s all right?</p>
<p>A: Sure, let’s do that instead!</p>
<h2 id="lets-play">Let’s play!</h2>
<p>A: What we have so far is still a bit awkward to use. Sure, we can read
in many things one after another, but it doesn’t seem like we can handle
more than one thing at a time. What if we want to combine two inputs?</p>
<p>B: Yeah, that’s true – we can’t bind new variables on the fly, at least
not without interrupting the nice <code>do</code>-way of writing things.</p>
<p>A: Hm, let me think … I’m pretty sure we can tweak the <code>state</code> of our monad
a bit to simulate something like variables …</p>
<p>B: … I guess?</p>
<p>A: … it’d also be useful if we had more than just sequences of functions.
What about loops, and conditions, and all that stuff? I’m sure we can add
that to …</p>
<p>B: … I’m not sure that’s a good idea. Do you that’s even doable?</p>
<p>A: Yep! In fact, I’ve done it already: <a href="https://stuebinm.eu/git/playground/tree/nix-turing/io.nix">io.nix</a>. I also wrote
a version of tic tac toe in it:</p>
<div class="sourceCode" id="cb29"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb29-1"><a href="#cb29-1" aria-hidden="true" tabindex="-1"></a>...</span>
<span id="cb29-2"><a href="#cb29-2" aria-hidden="true" tabindex="-1"></a><span class="op">{</span><span class="va">seed</span><span class="op">}</span>:</span>
<span id="cb29-3"><a href="#cb29-3" aria-hidden="true" tabindex="-1"></a><span class="kw">with</span> <span class="bu">import</span> <span class="ss">./io.nix</span> <span class="op">{</span><span class="va">s</span> <span class="op">=</span> seed<span class="op">;};</span></span>
<span id="cb29-4"><a href="#cb29-4" aria-hidden="true" tabindex="-1"></a>do initialWorld <span class="op">[</span></span>
<span id="cb29-5"><a href="#cb29-5" aria-hidden="true" tabindex="-1"></a>  <span class="op">(</span><span class="va">v</span><span class="op">:</span> assign <span class="st">&quot;grid&quot;</span> emptyGrid<span class="op">)</span></span>
<span id="cb29-6"><a href="#cb29-6" aria-hidden="true" tabindex="-1"></a>  <span class="op">(</span><span class="va">v</span><span class="op">:</span> assign <span class="st">&quot;turn&quot;</span> <span class="st">&quot;x&quot;</span><span class="op">)</span></span>
<span id="cb29-7"><a href="#cb29-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb29-8"><a href="#cb29-8" aria-hidden="true" tabindex="-1"></a>  <span class="op">(</span>while <span class="op">(</span><span class="va">v</span><span class="op">:</span> <span class="op">!(</span>hasWon <span class="st">&quot;x&quot;</span> v.grid<span class="op">)</span> <span class="op">&amp;&amp;</span> <span class="op">!(</span>hasWon <span class="st">&quot;o&quot;</span> v.grid<span class="op">))</span></span>
<span id="cb29-9"><a href="#cb29-9" aria-hidden="true" tabindex="-1"></a>    <span class="op">(</span>ifThenElse <span class="op">(</span><span class="va">v</span><span class="op">:</span> v.turn == <span class="st">&quot;o&quot;</span><span class="op">)</span></span>
<span id="cb29-10"><a href="#cb29-10" aria-hidden="true" tabindex="-1"></a>      <span class="op">(</span><span class="va">v</span><span class="op">:</span> doMonadic <span class="op">[</span></span>
<span id="cb29-11"><a href="#cb29-11" aria-hidden="true" tabindex="-1"></a>        <span class="op">(</span><span class="va">v</span><span class="op">:</span> read_input <span class="st">&quot;test&quot;</span><span class="op">)</span></span>
<span id="cb29-12"><a href="#cb29-12" aria-hidden="true" tabindex="-1"></a>        <span class="op">(</span>ifThenElse <span class="op">(</span><span class="va">v</span><span class="op">:</span> v.grid.$<span class="op">{</span><span class="va">stripWhitespace</span> <span class="va">v</span>.<span class="va">input</span><span class="op">}</span> == <span class="st">&quot; &quot;</span><span class="op">)</span></span>
<span id="cb29-13"><a href="#cb29-13" aria-hidden="true" tabindex="-1"></a>          <span class="op">(</span><span class="va">v</span><span class="op">:</span> doMonadic <span class="op">[</span></span>
<span id="cb29-14"><a href="#cb29-14" aria-hidden="true" tabindex="-1"></a>            <span class="op">(</span><span class="va">v</span><span class="op">:</span> assign <span class="st">&quot;grid&quot;</span></span>
<span id="cb29-15"><a href="#cb29-15" aria-hidden="true" tabindex="-1"></a>              <span class="op">(</span>v.grid <span class="op">//</span> <span class="op">{</span> ${<span class="va">stripWhitespace</span> <span class="va">v</span>.<span class="va">input</span><span class="op">}</span> = <span class="st">&quot;o&quot;</span>; }<span class="op">))</span></span>
<span id="cb29-16"><a href="#cb29-16" aria-hidden="true" tabindex="-1"></a>            <span class="op">(</span><span class="va">v</span><span class="op">:</span> print <span class="op">(</span>showGrid v.grid<span class="op">))</span></span>
<span id="cb29-17"><a href="#cb29-17" aria-hidden="true" tabindex="-1"></a>            <span class="op">(</span><span class="va">v</span><span class="op">:</span> assign <span class="st">&quot;turn&quot;</span> <span class="st">&quot;x&quot;</span><span class="op">)</span></span>
<span id="cb29-18"><a href="#cb29-18" aria-hidden="true" tabindex="-1"></a>          <span class="op">])</span></span>
<span id="cb29-19"><a href="#cb29-19" aria-hidden="true" tabindex="-1"></a>          <span class="op">(</span><span class="va">v</span><span class="op">:</span> print <span class="st">&quot;this is not a legal move, try again!&quot;</span><span class="op">)</span></span>
<span id="cb29-20"><a href="#cb29-20" aria-hidden="true" tabindex="-1"></a>        <span class="op">)</span></span>
<span id="cb29-21"><a href="#cb29-21" aria-hidden="true" tabindex="-1"></a>      <span class="op">])</span></span>
<span id="cb29-22"><a href="#cb29-22" aria-hidden="true" tabindex="-1"></a>      <span class="op">(</span><span class="va">v</span><span class="op">:</span> doMonadic <span class="op">[</span></span>
<span id="cb29-23"><a href="#cb29-23" aria-hidden="true" tabindex="-1"></a>        <span class="op">(</span><span class="va">v</span><span class="op">:</span> assign <span class="st">&quot;grid&quot;</span> <span class="op">(</span>v.grid <span class="op">//</span> <span class="op">{</span> ${<span class="va">maximalMove</span> <span class="va">v</span>.<span class="va">grid</span><span class="op">}</span> = <span class="st">&quot;x&quot;</span>; }<span class="op">))</span></span>
<span id="cb29-24"><a href="#cb29-24" aria-hidden="true" tabindex="-1"></a>        <span class="op">(</span><span class="va">v</span><span class="op">:</span> print <span class="op">(</span>showGrid v.grid<span class="op">))</span></span>
<span id="cb29-25"><a href="#cb29-25" aria-hidden="true" tabindex="-1"></a>        <span class="op">(</span><span class="va">v</span><span class="op">:</span> assign <span class="st">&quot;turn&quot;</span> <span class="st">&quot;o&quot;</span><span class="op">)</span></span>
<span id="cb29-26"><a href="#cb29-26" aria-hidden="true" tabindex="-1"></a>      <span class="op">])</span></span>
<span id="cb29-27"><a href="#cb29-27" aria-hidden="true" tabindex="-1"></a>    <span class="op">)</span></span>
<span id="cb29-28"><a href="#cb29-28" aria-hidden="true" tabindex="-1"></a>  <span class="op">)</span></span>
<span id="cb29-29"><a href="#cb29-29" aria-hidden="true" tabindex="-1"></a>  <span class="op">(</span>match <span class="op">(</span><span class="va">v</span><span class="op">:</span> gameState v.grid<span class="op">)</span> <span class="op">{</span></span>
<span id="cb29-30"><a href="#cb29-30" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;</span>draw<span class="st">&quot;</span> <span class="op">=</span> <span class="va">v</span><span class="op">:</span> print <span class="st">&quot;this game ended in a draw&quot;</span><span class="op">;</span></span>
<span id="cb29-31"><a href="#cb29-31" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;o&quot;</span> <span class="op">=</span> <span class="va">v</span><span class="op">:</span> print <span class="st">&quot;o won this game&quot;</span><span class="op">;</span></span>
<span id="cb29-32"><a href="#cb29-32" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;x&quot;</span> <span class="op">=</span> <span class="va">v</span><span class="op">:</span> print <span class="st">&quot;x won this game&quot;</span><span class="op">;</span></span>
<span id="cb29-33"><a href="#cb29-33" aria-hidden="true" tabindex="-1"></a>  <span class="op">})</span></span>
<span id="cb29-34"><a href="#cb29-34" aria-hidden="true" tabindex="-1"></a><span class="op">]</span></span></code></pre></div>
<p>A: Well, that’s only the Monad-part of it; there’s lots of helper functions.
The entire thing is in <a href="https://stuebinm.eu/git/playground/tree/nix-turing/game.nix">game.nix</a>.</p>
<p>It actually works, too!</p>
<p>B: <em>sigh</em></p>
<p>A: Just run <code>nix-build -argstr seed 4</code> on that, then write all of your moves
into the tmp files it tells you to. But make sure to never take more than
three seconds to decide your next move, or it’ll fail to read in the next
file and everything will crash!</p>
<p>On the plus side, if it does crash, you can restart where you left of by just
running the same command again – maximal lazyness shold take care of the
rest.</p>
<p>I guess this should count as proof that, in addition to being Turing-complete,
Nix is also <a href="https://www.youtube.com/watch?v=X36ye-1x_HQ&amp;t=245s">tic-tac-toe complete</a>?</p>
<p>B: … I don’t know why I put up with you.</p>
<h2 id="addendum">Addendum</h2>
<p>A: So, does all of this actually mean anything?</p>
<p>B: Apart from that Nix kinda has impure functions, if you squint hard enough?</p>
<p>A: Well, we already knew that before – even without impure things like
<code>builtins.fetchGit</code> that don’t require hashes, reading in files from outside
the Nix store without requiring hashes will <em>always</em> be impure. It’s just
so happens to also be very convenient.</p>
<p>B: I guess so. Nix doesn’t usually come with an I/O monad, though.</p>
<p>A: Yeah, that’s true. Blame this post for the monad, if you like.</p>
]]></description>
    <pubDate>Sat, 13 Nov 2021 00:00:00 UT</pubDate>
    <guid>https://stuebinm.eu/posts/nix-tic-tac-toe-complete.html</guid>
    <dc:creator>terru</dc:creator>
</item>

    </channel>
</rss>
