<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-03-26T00:53:12+01:00</updated><id>/feed.xml</id><title type="html">Andrew Shaw</title><subtitle>Personal website and blog</subtitle><entry><title type="html">Reviving Genius</title><link href="/2025/07/07/reviving-genius.html" rel="alternate" type="text/html" title="Reviving Genius" /><published>2025-07-07T00:00:00+02:00</published><updated>2025-07-07T00:00:00+02:00</updated><id>/2025/07/07/reviving-genius</id><content type="html" xml:base="/2025/07/07/reviving-genius.html"><![CDATA[<blockquote>
  <p><strong>EDIT</strong>: I’ve had some feedback from <a href="https://news.ycombinator.com/item?id=44540724">HN readers</a>, and I’ve learned some interesting things since writing this post!</p>

  <p>For one, after this whole journey I thought that <em>surely</em> it is easy enough to type in answers on Anki, and indeed it is. In the card’s template, you can add an entry <code class="language-plaintext highlighter-rouge">{type:FieldName}</code> to the front, where you are asked ot type in whatever <code class="language-plaintext highlighter-rouge">FieldName</code> is. So in my “Frequency Dictionary of Dutch” deck, I’ve added <code class="language-plaintext highlighter-rouge">type:Definition</code>.</p>

  <p>Secondly, I actually <a href="https://docs.ankiweb.net/deck-options.html#learning-steps">read the manual for Anki</a>, and they have a great explanation of what the <em>Again / Hard / Good / Easy</em> buttons actually <em>do</em> to the underlying repetition model. What I liked about Genius was how straightforward it was to understand <em>why</em> you’re seeing or not something during a review session.</p>

  <p>Finally the post had some typos, and a couple of errors. Thanks to <code class="language-plaintext highlighter-rouge">aaronbrethorst</code> for pointing out that <code class="language-plaintext highlighter-rouge">+</code> and <code class="language-plaintext highlighter-rouge">-</code> are for instance, and class methods, not public/private.</p>
</blockquote>

<p>In my recent foray into learning Dutch for my relocation to the Netherlands – I’m sure there’ll be much to blog about – I fell victim to the siren song of the Green Owl again.</p>

<p>It’s too tempting - you get showered with instant feedback, encouragement in-app rewards, social stuff etc. To be fair it is fun, and you really feel that you’re doing something useful.</p>

<p>…except.</p>

<p>I last used it in earnest it to ‘learn’ German before a trip to Berlin. I racked up a 150+ day streak or something nuts like that, a good 15 mins per day which they deem pretty high, and then turned up utterly unable to speak or read anything of actual utility</p>

<blockquote>
  <p>die Frau ist kein Klavier</p>

  <p>am Dienstag spreche ich nicht mit dem Apfel</p>
</blockquote>

<p>Now I don’t think it was always like this, and really it’s for another blog post to enumerate the all of the ways it’s been enshittified but suffice to say it’s just not for me anymore.</p>

<h2 id="flashcards-and-anki">Flashcards and Anki</h2>

<p>On the other end of this gamification spectrum is just hard flashcards. Learn the thing on the other side of this card. Your reward is the number of stuff you’ve learned, and if you ‘break your streak’, that’s on you - you’re just not learning anything.</p>

<p>Now, people swear by Anki.</p>

<p>I’ve friends who’ve learned all the world flags, taught themselves to recognise basically any painter by their style etc. Really cool stuff. I think though, for whatever way I ingest language and information, it’s not so good for me.</p>

<p>Specifically I think I’m missing clear right/wrong feedback (rather than judging if I was ‘good’ or ‘bad’ at remembering it), as well as the mechanics of actually having to type out the answer.</p>

<p>In school and univeristy I found that unless I was actually taking notes, and actually writing text, or even typing, I was just <em>not</em> engaging enough.</p>

<p>And so, when flipping over Anki cards and saying them in my head, it just sort of feels like temporarily stimulation of short-term memory.</p>

<p>Despite the spaced repetition, nothing seems to go in, and so I’ve just not been able to build a good habit with it.</p>

<p>(I think there are cards where you have to type the answer in, but truth be told I haven’t really dug into that)</p>

<p>Now though this brings me to my holy grail, white whale app, from <strong>years</strong> gone by.</p>

<h2 id="genius">Genius</h2>

<p>Genius was a spaced repetition app originally written by <a href="https://jrcpl.us/">John R Chang</a> for his own needs. It uses a comparatively simple (to Anki) but still very effective spaced repetition app for learning flashcard-like associations.</p>

<p>And, it was <em>so simple</em>. You have 2 columns to enter the things you want to learn (the cue), and you’re quizzed on them (the answer).</p>

<p>Plus, it had <em>just</em> the right amount of instant-gratification, reward conditioning sounds in the form of the OS X <em>Basso</em>, <em>Blow</em>, <em>Funk</em>.</p>

<p>Alas, development stopped sometime around 2008, and despite an impressive run, it no longer runs on macOS:</p>

<pre><code class="language-txt">/Volumes/Genius/Genius.app/Contents/MacOS
❯ file Genius
Genius: Mach-O universal binary with 2 architectures: [ppc:
- Mach-O executable ppc] [i386:
- Mach-O executable i386]
Genius (for architecture ppc):  Mach-O executable ppc
Genius (for architecture i386): Mach-O executable i386
</code></pre>

<p>Yes, PowerPC, and i386 (32 bit) only. We’re a few architectures past that!</p>

<h2 id="reviving-old-software">Reviving Old Software</h2>

<p>As an aside, I’ve been really impressed by some recent really cool software/tech conservation, poeple are doing.</p>

<p>See stuff like <a href="https://www.youtube.com/watch?v=gthm-0Av93Q">MattKC and co’s Lego Island decompilation</a> where they have reverse-engineered and entirely reimplemented whole swathes of the game. Or the <a href="https://github.com/Kneesnap/onstream-data-recovery/blob/main/info/INTRO.MD">Frogger source code recovery</a></p>

<p>Or on the hardware side, <a href="https://www.youtube.com/watch?v=JfZxOuc9Qwk">the saving of the world’s largest CRT TV from a closing-down soba restaurant in Osaka</a> (this is seriously a good watch).</p>

<p>Anyway I figured, if feckin <em>Lego Island</em> can be decompiled, and people can save code from ancient proprietary magnetic tapes, surely it’s not beyond the wit of man, me, to get Genius to work again.</p>

<p><em>Maybe I can be that developer :)</em></p>

<h2 id="source-code">Source Code</h2>

<p>The homepage is <a href="https://genius.sourceforge.net">https://genius.sourceforge.net</a>. With some clicking about, you’ll find the link to the <a href="https://sourceforge.net/p/genius/code/HEAD/tree/">source code</a>, an SVN repo. I’d only ever heard of SVN from my elders in university - something about locking, a ‘check out’ being an actual ‘check out’ as one aquires an exclusive lock on a library book.</p>

<p>I’m not interested in plumbing those depths, but luckily <a href="https://git-scm.com/docs/git-svn"><code class="language-plaintext highlighter-rouge">git-svn</code></a> is.</p>

<p>So after much waiting, I ended up with a neat git repo. It’s jarring to pop open a familiar git log and see commits from <em>2004</em>:</p>

<pre><code class="language-txt">commit 5d5624365ab06b08bb83257e78d1bc44bbca7664
Author: jrc &lt;jrc@f73e0bdd-8638-0410-8bb3-c52e70a1423d&gt;
Date:   Sat Nov 6 02:29:42 2004 +0000

    loc 1

commit a2045bf7f2d5ed93f73893edce72665ea260453e
Author: jrc &lt;jrc@f73e0bdd-8638-0410-8bb3-c52e70a1423d&gt;
Date:   Sat Nov 6 02:28:42 2004 +0000

    *** empty log message ***

commit 1d9e71e3fafe91d182a244b3bf5fc1173348dfcb
Author: jrc &lt;jrc@f73e0bdd-8638-0410-8bb3-c52e70a1423d&gt;
Date:   Tue Oct 19 21:53:33 2004 +0000

    This commit was generated by cvs2svn to compensate for changes in r2,
    which included commits to RCS files with non-trunk default branches.

commit 62485d30ab237f05d04e2791a9e6871b9adbf701
Author: (no author) &lt;(no author)@f73e0bdd-8638-0410-8bb3-c52e70a1423d&gt;
Date:   Tue Oct 19 21:53:33 2004 +0000

    New repository initialized by cvs2svn.
</code></pre>

<p>I love how everything repeats itself. I wonder what was in the CVS source.</p>

<h2 id="bulding-for-macos">Bulding for macOS</h2>

<p>As it turns out, it was surprisingly easy to get the project to build!</p>

<p>The <code class="language-plaintext highlighter-rouge">.xcodeproj</code> format still seems to be entirely forwards-compatible, and it opened up completely fine on the latest developer tools, on my arm64 macbook.</p>

<p>Building failed, as it was unable to locate the old OS X 10.4 SDK it was hard-coded to require.</p>

<p>But changing this to the latest tooling let the obj-c compiler actually run, with a compiler error!</p>

<div class="language-objective-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">GeniusAssociationEnumerator</span><span class="p">.</span><span class="n">m</span><span class="o">:</span><span class="mi">265</span><span class="o">:</span><span class="mi">87</span> <span class="n">Incompatible</span> <span class="n">function</span> <span class="n">pointer</span> <span class="n">types</span> <span class="n">sending</span> 
  <span class="err">'</span><span class="kt">int</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">id</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="err">'</span> <span class="n">to</span> <span class="n">parameter</span> <span class="n">of</span> <span class="n">type</span> 
  <span class="err">'</span><span class="n">NSInteger</span> <span class="p">(</span><span class="o">*</span> <span class="n">_Nonnull</span><span class="p">)(</span><span class="n">id</span> <span class="n">_Nonnull</span><span class="p">,</span> <span class="n">id</span> <span class="n">_Nonnull</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span> <span class="n">_Nullable</span><span class="p">)</span><span class="err">'</span> 
    <span class="p">(</span><span class="n">aka</span> <span class="err">'</span><span class="kt">long</span> <span class="p">(</span><span class="o">*</span><span class="p">)(</span><span class="n">id</span> <span class="n">_Nonnull</span><span class="p">,</span> <span class="n">id</span> <span class="n">_Nonnull</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span> <span class="n">_Nullable</span><span class="p">)</span><span class="err">'</span><span class="p">)</span>
</code></pre></div></div>

<p>Looks like type checking got a little bit more strict.</p>

<p>A <em>single line change</em> fixed the build:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//! randomly returns NSOrderedAscending or NSOrderedDescending
<span class="gd">-int RandomSortFunction(id object1, id object2, void * context)
</span><span class="gi">+NSComparisonResult RandomSortFunction(id object1, id object2, void * context)
</span> {
     BOOL x = random() &amp; 0x1;
     return (x ? NSOrderedAscending : NSOrderedDescending);
</code></pre></div></div>

<p><a href="https://github.com/shawa/genius/commit/39f26bed7e5bde146bd05aadacbe33e2d2432386#diff-dd1391d7c026c37eb147d2c8adace5dbd76bbfb738780d502584f51b9f6c281eR16-L37"><code class="language-plaintext highlighter-rouge">GeniusAssociationEnumerator.m</code></a></p>

<p><em>As an aside, surely it’s really confusing as an Objective-C developer reading diffs when it uses <code class="language-plaintext highlighter-rouge">+</code> and <code class="language-plaintext highlighter-rouge">-</code> to denote class/instance methods?</em></p>

<p>…and it <strong>ACTUALLY BUILT</strong>.</p>

<p>It looks a bit janky, but it works!</p>

<p>The next hurdle was getting Xcode to read the old <code class="language-plaintext highlighter-rouge">nib</code> files for the UI. I’ve admittedly little idea how it works, but they seem so old that even Xcode can’t convert them. Fortunately there is <code class="language-plaintext highlighter-rouge">ibtool</code>, which has an <code class="language-plaintext highlighter-rouge">--upgrade</code> option to replace them.</p>

<p>After a little bit of messing about in Interface Builder, upscaling the icons, and fixing some text clipping, we get a fairly snazzy, modern looking macOS app! And it’s completely functional, fully compatible with the original <code class="language-plaintext highlighter-rouge">.genius</code> card decks I created years ago.</p>

<p>Projects like this are certainly fun, and valuable in their own right. I’ve learned a relative ton (compared to my zero knowledge) about mac development, took a nice trip down memory lane with the different architectures, etc.</p>

<p>However ultimately Genius was written to aid memorisation, and now I’m happily using it to do just that, just as I did back in 2008!</p>

<p>You can browse the updated source code at <a href="https://github.com/shawa/genius">https://github.com/shawa/genius</a>.</p>]]></content><author><name></name></author><category term="objective-c" /><category term="macOS" /><summary type="html"><![CDATA[EDIT: I’ve had some feedback from HN readers, and I’ve learned some interesting things since writing this post! For one, after this whole journey I thought that surely it is easy enough to type in answers on Anki, and indeed it is. In the card’s template, you can add an entry {type:FieldName} to the front, where you are asked ot type in whatever FieldName is. So in my “Frequency Dictionary of Dutch” deck, I’ve added type:Definition. Secondly, I actually read the manual for Anki, and they have a great explanation of what the Again / Hard / Good / Easy buttons actually do to the underlying repetition model. What I liked about Genius was how straightforward it was to understand why you’re seeing or not something during a review session. Finally the post had some typos, and a couple of errors. Thanks to aaronbrethorst for pointing out that + and - are for instance, and class methods, not public/private.]]></summary></entry><entry><title type="html">Imbolc</title><link href="/2025/02/02/imbolc.html" rel="alternate" type="text/html" title="Imbolc" /><published>2025-02-02T00:00:00+01:00</published><updated>2025-02-02T00:00:00+01:00</updated><id>/2025/02/02/imbolc</id><content type="html" xml:base="/2025/02/02/imbolc.html"><![CDATA[<p>Yesterday, the first of February, marked <a href="https://en.wikipedia.org/wiki/Imbolc">Imbolc</a> or Lá Fhéile Bhríde in Ireland.
It is also fitting that today is the first day I can recall in months of waking to sunlight streaming into my bedroom.
A much welcome change from the thick, duvet-like cloud cover has been blanketing London lately.</p>

<p>In school, I had always found the months of the year, and the seasons to be somewhat arbitrary.
Growing up in Ireland, that calendars are quite arbitrary was somewhat obvious - most of the media we consumed (from Britain and the USA) was from places where the first day of the week is Monday, whilst we were always taught that <em>Dé Domhnaigh</em>, Sunday, was the first day of the week.</p>

<p>It was only in university when we learned the agony of programming dates and times that I actually appreciated how utterly arbitrary the Gregorian calendar is.</p>

<p>Take how September, October, November, December have latin-name prefixes (7, 8, 9, 10…), whilst they actually seem to fall two months too late.</p>

<p>Supposedly this was because emperors Julius and Augustus wanted some calendar action, and so they squeezed in some months of their own mid year (presumably in the summer months, so people would associate them with good weather and jet-setting abroad).</p>

<p>So, calendars are somewhat arbitrary - we have to mark out time somehow, and that’s inevitably going to lead to having to simply make some choices.</p>

<p>Similar to the days of the week, the seasons in Ireland are shifted somewhat. The traditional calendar has Spring, or <em>Earrach</em> start in <strong>February</strong>, rather than March, like in Britain and I suppose the rest of the world.</p>

<p>Wikipedia has a <a href="https://en.wikipedia.org/wiki/Irish_calendar#/media/File:Comparative_seasons_wheel.jpg">great graphic</a> showing the structure of the ancient Irish calendar, which is, on the contrary, entirely rooted in the solar cycle. <em>Festivals</em> fall at the midpoint between a solstice and an equinox, and are celebrated to mark the changing of the <em>Seasons</em>.</p>

<p>I liked this graphic so much that I wanted to animate my own.
The below graphic is the essence of that same SVG, but drawn with the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">Canvas API</a>.</p>

<p>The quadrants are the seasons, the label in the middle are their names. and their start and end points mark the changes of each season.</p>

<p>The labels in the middle show the season names, whilst the dotted lines mark the solar events marking their midpoints.</p>

<style>
  .calendar-container {
    display: flex;
    justify-content: center;
    align-items: center;
  }

  dt {
    font-weight: bold
  }

  dd {
    font-style: italic
  }
</style>

<div class="calendar-container">
  <canvas id="seasonalWheel" width="600" height="600"></canvas>
</div>

<dl>
  <dt>Samhain</dt>
  <dd>Beginning of Winter</dd>

  <dt>Geimhreadh</dt>
  <dd>Winter</dd>

  <dt>Grianstad an Gheimhridh</dt>
  <dd>Winter Solstice</dd>

  <dt>Imbolc</dt>
  <dd>Beginning of Spring</dd>

  <dt>Earrach</dt>
  <dd>Spring</dd>

  <dt>Cónocht an Earraigh</dt>
  <dd>Spring Equinox</dd>

  <dt>Bealtaine</dt>
  <dd>Beginning of Summer</dd>

  <dt>Grianstad an tSamhraidh</dt>
  <dd>Summer Solstice</dd>

  <dt>Samradh</dt>
  <dd>Summer</dd>

  <dt>Lughnasadh</dt>
  <dd>Beginning of Autumn (Harvest Season)</dd>

  <dt>Cónocht an Fhómhair</dt>
  <dd>Autumnal Equinox</dd>

  <dt>Fómhar</dt>
  <dd>Autumn</dd>
</dl>

<p>There is one more dotted line which today is colliding with <em>IMBOLC</em>’s line. This will update on page-load to the current day, marking where we are with respect to the seasons.</p>

<p>I quite enjoyed cobbling this together and re-learning about the seasons.</p>

<p>I appreciate the structure of it and how it simply relates to the earth and the sun. Now, with climate change, God knows how the real seasons are going to drift from how they were for year.</p>

<p>Sadly maybe this will become another calendar that looks completely arbitrary, but for the moment it’s nice to pause and appreciate the intuitive pragmatism of older ways.
Now, there is an entire world, dimension even of exploration into the history of ancient Irish festivals which I don’t have the confidence to explain without completely butchering it.</p>

<p>I’d sooner recommend you do a bit of searching.
Manchán Magan has a <a href="https://www.youtube.com/watch?v=oCoHJBK89KE">great recording on <em>Geata Arts</em></a> chatting about some of the oldest customs that could be gathered from community elders:</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/oCoHJBK89KE?si=20UampuepBaOpoOu" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>

<p>Wishing all a nice bright Earrach!</p>

<script>
const canvas = document.getElementById('seasonalWheel');
const ctx = canvas.getContext('2d');
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(centerX, centerY) - 150;

const toRadians = degrees => degrees * Math.PI / 180;

function drawQuadrant(startAngle, color) {
  ctx.beginPath();
  ctx.moveTo(centerX, centerY);
  ctx.arc(centerX, centerY, radius, toRadians(startAngle), toRadians(startAngle + 90));
  ctx.lineTo(centerX, centerY);
  ctx.fillStyle = color;
  ctx.fill();
  ctx.stroke();
}

function drawDiagonal(angle) {
  const x = centerX + radius * Math.cos(toRadians(angle));
  const y = centerY + radius * Math.sin(toRadians(angle));
  
  ctx.beginPath();
  ctx.setLineDash([5, 5]);
  ctx.moveTo(centerX, centerY);
  ctx.lineTo(x, y);
  ctx.stroke();
  ctx.setLineDash([]);
}

function addLabel(text, in_angle, distance) {
  const angle = in_angle - 90
  const x = centerX + distance * Math.cos(toRadians(angle));
  const y = centerY + distance * Math.sin(toRadians(angle));
  
  ctx.save();
  ctx.font = '14px Arial';
  ctx.textAlign = 'left';
  ctx.textBaseline = 'middle';
  
  ctx.translate(x, y);
  ctx.rotate(toRadians(angle));
  
  ctx.fillStyle = 'black';
  ctx.fillText(text, 0, 0);
  ctx.restore();
}

function drawTodayLine() {
    const today = new Date();
    const dayOfYear = Math.floor((today - new Date(today.getFullYear(), 0, 0)) / (1000 * 60 * 60 * 24))
    const angle = dayOfYear / 365.0

    drawDiagonal(angle);
    addLabel('INNIU', angle + 90, radius - 30)
}

drawQuadrant(-90, 'blue');
drawQuadrant(0, 'green');
drawQuadrant(90, 'yellow');
drawQuadrant(180, 'orange');

drawDiagonal(45);
drawDiagonal(225);
drawDiagonal(135);
drawDiagonal(315);

drawTodayLine();

addLabel('SAMHAIN', 0, radius +10);
addLabel('IMBOLC', 90, radius + 10);
addLabel('BEALTAINE', 180, radius + 10);
addLabel('LUGHNASADH', 270, radius + 10);

addLabel('Grianstad an Gheimhridh', 45, radius + 10);
addLabel('Cónocht an Earraigh', 135, radius + 10);
addLabel('Grianstad an tSamhraidh', 225, radius + 10);
addLabel('Cónocht an Fhómhair', 315, radius + 10);

addLabel('Geimhreadh', 45 - 20, 30);
addLabel('Earrach', 135 - 20, 30);
addLabel('Samradh', 225 - 20, 30);
addLabel('Fómhar', 315 - 20, 30);
</script>]]></content><author><name></name></author><category term="ireland" /><category term="celtic" /><category term="seasons" /><category term="reflection" /><summary type="html"><![CDATA[Yesterday, the first of February, marked Imbolc or Lá Fhéile Bhríde in Ireland. It is also fitting that today is the first day I can recall in months of waking to sunlight streaming into my bedroom. A much welcome change from the thick, duvet-like cloud cover has been blanketing London lately.]]></summary></entry><entry><title type="html">Web Posts For All</title><link href="/2025/01/26/web-posts-for-all.html" rel="alternate" type="text/html" title="Web Posts For All" /><published>2025-01-26T00:00:00+01:00</published><updated>2025-01-26T00:00:00+01:00</updated><id>/2025/01/26/web-posts-for-all</id><content type="html" xml:base="/2025/01/26/web-posts-for-all.html"><![CDATA[<p>In <a href="/blog/rss-really-simple">RSS is Really Simple</a> I conjectured that the state of the internet would be very different and to be honest better, had we elided these closed platform in favour of something open like simple RSS.</p>

<p>I’m enjoying the longer form concept of the blog. It’s making me realise how since school and university how little time I actually spend putting pen to paper and thinking and writing about something for more than five seconds.
‘m also finding myself more inclined to spend commuting time a little bit more mindfully and pick up notes and jot something down rather than, say, desperately scroll on patchy phone signal.</p>

<p>All this said though, there is some role still for shorter form micro blogging. The halcyon days of the golden era of Irish Twitter, of bread flying off the shelves during a storm, yer man <a href="https://threadreaderapp.com/thread/992006545473966082.html">doing ketamaine while serving drinks to the president</a>, the Luas is free…</p>

<p>Twitter’s fate is sealed of course, and I have no interest in throwing my wailings about what’s going on with it, and what that represents into the chasm of gloom. All I know is I want out, so I’ve archived and deleted my account.</p>

<p>That brings us to what I’m doing in its place. Bluesky is cool but it’s still another enclosed platform. I’m sure ActivityPub or the AT protocol or whatever it uses is cool and all but I’m not interested really. What I <em>am</em> interested in is how I can generate more pages for my website.</p>

<p>Enter the <em>Web Posts</em>. Another fine invention of <code class="language-plaintext highlighter-rouge">andrewshaw.nl</code>.</p>

<p>I have taken the liberty to reinvent and reimplement the format. Within my site’s codebase I have a bag of markdown files which all get hoovered up at page generation time. The creation time of the file is taken to be the post date, and they all get stuffed into <a href="/posts">/posts.html</a>.</p>

<p>Now I don’t carry my laptop everywhere, and I can’t regenerate the site on the go, <strong>and</strong> I don’t want to turn up a whole backend to accept posts (the site is still static HTML), so instead I have a neat little workflow using iCloud and shortcuts:</p>

<p>This gets saved into a markdown file in my iCloud Drive, and then at generation time, these are all copied over with the rest of the posts.</p>

<p>For the moment I think it’s very neat. Since it’s markdown, of course I can do whatever I like and use mad formatting, gifs, iframes, whatever.</p>]]></content><author><name></name></author><category term="internet" /><category term="twitter" /><category term="rss" /><summary type="html"><![CDATA[In RSS is Really Simple I conjectured that the state of the internet would be very different and to be honest better, had we elided these closed platform in favour of something open like simple RSS.]]></summary></entry><entry><title type="html">RSS really is “Really Simple”</title><link href="/2025/01/26/rss-really-simple.html" rel="alternate" type="text/html" title="RSS really is “Really Simple”" /><published>2025-01-26T00:00:00+01:00</published><updated>2025-01-26T00:00:00+01:00</updated><id>/2025/01/26/rss-really-simple</id><content type="html" xml:base="/2025/01/26/rss-really-simple.html"><![CDATA[<p>Following some intense lobbying from fellow netizen Killian (see his website at <a href="https://killiandavitt.me">https://killiandavitt.me</a>) I’m pleased to announce that I’ve added an RSS feed to the site.</p>

<p>Surprisingly there aren’t that many libraries for building RSS feeds for Elixir. Perhaps that’s not surprising, since it seems RSS has fallen out of favour in deference to integrated social media platforms. Alas.</p>

<p>But perhaps also it’s not ever been necessary. As it turns out RSS stands for “Really Simple Syndication”, and it really is, really simple.</p>

<p>Your RSS “feed” is an XML document, which contains a <code class="language-plaintext highlighter-rouge">channel</code>, with metadata, and <code class="language-plaintext highlighter-rouge">items</code> which are your posts.</p>

<p>These posts are just elements which themselves contain metadata like the title, publication date and a summary as well as an ID, so that RSS readers can detect whether it’s seen a post before (so that it can decide if it’s a brand new post, or just one which has been updated).</p>

<p>And as it turns out generating all this stuff with EEx is very very easy. Originally I was considering writing a library that represents feeds, channels and posts as structs, and then serialise that to XML,  but as it turns out I can just generate the file directly in one shot. Seriously, this is the entire template:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="cp">&lt;?xml version="1.0" encoding="UTF-8" ?&gt;</span>
<span class="nt">&lt;rss</span> <span class="na">version=</span><span class="s">"2.0"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;channel&gt;</span>
        <span class="nt">&lt;title&gt;</span>andrewshaw.nl<span class="nt">&lt;/title&gt;</span>
        <span class="nt">&lt;link&gt;</span>https://andrewshaw.nl<span class="nt">&lt;/link&gt;</span>
        <span class="nt">&lt;description&gt;</span>Andrew Shaw's personal website<span class="nt">&lt;/description&gt;</span>
        <span class="nt">&lt;language&gt;</span>en-ie<span class="nt">&lt;/language&gt;</span>
        <span class="nt">&lt;pubDate&gt;</span><span class="err">&lt;</span>%= DateTime.utc_now() |&gt; DateTime.to_iso8601() %&gt;<span class="nt">&lt;/pubDate&gt;</span>

        <span class="err">&lt;</span>%= for post <span class="err">&lt;</span>- @posts do %&gt;
        <span class="nt">&lt;item&gt;</span>
            <span class="nt">&lt;title&gt;</span><span class="err">&lt;</span>%= post.title %&gt;<span class="nt">&lt;/title&gt;</span>
            <span class="nt">&lt;link&gt;</span>https://andrewshaw.nl/blog/<span class="err">&lt;</span>%= post.slug %&gt;.html<span class="nt">&lt;/link&gt;</span>
            <span class="nt">&lt;guid&gt;</span>https://andrewshaw.nl/blog/<span class="err">&lt;</span>%= post.slug %&gt;.html<span class="nt">&lt;/guid&gt;</span>
            <span class="nt">&lt;pubDate&gt;</span><span class="err">&lt;</span>%= post.date %&gt;<span class="nt">&lt;/pubDate&gt;</span>
            <span class="nt">&lt;description&gt;</span><span class="cp">&lt;![CDATA[&lt;%= post.abstract %&gt;]]&gt;</span><span class="nt">&lt;/description&gt;</span>
        <span class="nt">&lt;/item&gt;</span>
        <span class="err">&lt;</span>% end %&gt;
    <span class="nt">&lt;/channel&gt;</span>
<span class="nt">&lt;/rss&gt;</span>
</code></pre></div></div>

<p>Very nice altogether!</p>

<p>Through all of this, I do wonder where we lost our way. You can surely design a twitter-style microblogging site purely around these dead simple XML feeds. I wonder where we would be at if this were embraced instead of closed platforms. Probably fewer users I suppose.</p>]]></content><author><name></name></author><category term="internet" /><category term="rss" /><category term="xml" /><summary type="html"><![CDATA[Following some intense lobbying from fellow netizen Killian (see his website at https://killiandavitt.me) I’m pleased to announce that I’ve added an RSS feed to the site.]]></summary></entry><entry><title type="html">How the Web Site is built</title><link href="/2025/01/21/building-web-site.html" rel="alternate" type="text/html" title="How the Web Site is built" /><published>2025-01-21T00:00:00+01:00</published><updated>2025-01-21T00:00:00+01:00</updated><id>/2025/01/21/building-web-site</id><content type="html" xml:base="/2025/01/21/building-web-site.html"><![CDATA[<p><em><strong>NOTE</strong>: I took the photos down because I broke the build script and couldn’t publish new articles, and couldn’t be bothered to fix it. Sorry!</em></p>

<p>This homepage is half in-earnest technical project and half performance art. I wanted to keep things as simple, bare-bones, and extensible as I can, and to that end I opted initially for plain HTML and CSS. It’s surprising how far default browser rendering will get you!</p>

<p>After a few pages worth of copy-pasting I figured something more reusable would be better, and so looked back to the days of simple PHP includes. For the equivalent, Elixir comes with <a href="https://hexdocs.pm/eex/EEx.html">EEx</a> - <em>Embedded Elixir</em> for the templating of arbitrary text documents.
Its efficient implementation and swappable backends is what powers <a href="https://www.phoenixframework.org/">Phoenix and Phoenix LiveView</a>, and is more than suitable for my dingy needs.</p>

<p>In this post I’ll run through the overall structure, then break down custom sections like the blog and photo gallery. You can read this as part documentation and part tutorial. I’m not sure if I’ll bother sharing the code, but seriously, writing a static site generator is so easy that you can do pretty much all of this in an evening.</p>

<h2 id="the-build-script">The Build Script</h2>

<p>PHP, which powered my first ever one of these sort of sites, executes at response time, much like <a href="https://en.wikipedia.org/wiki/Common_Gateway_Interface">CGI</a>. The page content is dynamically generated at runtime. To contrast, static sites are… static, so all assets are generated up front on build/deployment time. Venerable Mix tasks are used to tie the whole thing together with <code class="language-plaintext highlighter-rouge">mix homepage.build</code> building out all of the HTML, <code class="language-plaintext highlighter-rouge">homepage.photos</code> running the photo ingestion pipeline (see below) and <code class="language-plaintext highlighter-rouge">homepage.deploy</code> shelling out to Cloudflare’s <code class="language-plaintext highlighter-rouge">wrangler CLI</code> to push a new deployment up to prod.</p>

<h2 id="the-layout">The Layout</h2>

<p>I have one top level template, which defines the root structure of each page, that is, the actual <code class="language-plaintext highlighter-rouge">&lt;html&gt;</code> element, the <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> etc. The navbar is generated in this template, from a master list of pages.</p>

<p>Inside the main template is the <code class="language-plaintext highlighter-rouge">&lt;%= @inner_content %&gt;</code> include which you may recognise from Phoenix templates:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;h1&gt;</span>Andrew Shaw<span class="nt">&lt;/h1&gt;</span>
<span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"content"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;nav&gt;</span>
        <span class="nt">&lt;ul&gt;</span>
        <span class="nt">&lt;</span><span class="err">%=</span> <span class="na">for</span> <span class="err">{</span><span class="na">page</span><span class="err">,</span> <span class="na">title</span><span class="err">}</span> <span class="err">&lt;</span><span class="na">-</span> <span class="err">@</span><span class="na">pages</span> <span class="na">do</span> <span class="err">%</span><span class="nt">&gt;</span>
            <span class="nt">&lt;li</span> <span class="na">class=</span><span class="s">"&lt;%= if page == @current_page, do: "</span><span class="na">active</span><span class="err">",</span> <span class="na">else:</span> <span class="err">""</span> <span class="err">%</span><span class="nt">&gt;</span>"&gt;
                <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"/&lt;%= page %&gt;.html"</span><span class="nt">&gt;&lt;</span><span class="err">%=</span> <span class="na">title</span> <span class="err">%</span><span class="nt">&gt;</span> <span class="nt">&lt;/a&gt;</span>
                <span class="nt">&lt;</span><span class="err">%=</span> <span class="na">if</span> <span class="na">page =</span><span class="s">=</span> <span class="err">"</span><span class="na">blog</span><span class="err">"</span> <span class="na">do</span> <span class="err">%</span><span class="nt">&gt;</span>
                    <span class="nt">&lt;img</span> <span class="na">class=</span><span class="s">"updated"</span> <span class="na">src=</span><span class="s">"/images/updated.gif"</span> <span class="nt">/&gt;</span>
                <span class="nt">&lt;</span><span class="err">%</span> <span class="na">end</span> <span class="err">%</span><span class="nt">&gt;</span>
            <span class="nt">&lt;/li&gt;</span>
        <span class="nt">&lt;</span><span class="err">%</span> <span class="na">end</span> <span class="err">%</span><span class="nt">&gt;</span>
        <span class="nt">&lt;/ul&gt;</span>
    <span class="nt">&lt;div&gt;</span>
        <span class="nt">&lt;img</span> <span class="na">id=</span><span class="s">"audio-button"</span> <span class="na">src=</span><span class="s">"/images/headphone-button.gif"</span> <span class="na">alt=</span><span class="s">"Toggle music"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;audio</span> <span class="na">id=</span><span class="s">"background-music"</span> <span class="na">loop</span><span class="nt">&gt;</span>
            <span class="nt">&lt;source</span> <span class="na">src=</span><span class="s">"/audio/iceland-tv-now.mp3"</span> <span class="na">type=</span><span class="s">"audio/mpeg"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;/audio&gt;</span>
    <span class="nt">&lt;/div&gt;</span>
    <span class="nt">&lt;/nav&gt;</span>
    <span class="nt">&lt;div&gt;</span>
        <span class="nt">&lt;</span><span class="err">%=</span> <span class="err">@</span><span class="na">inner_content</span> <span class="err">%</span><span class="nt">&gt;</span>
    <span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>

<p>On build, the main Mix task expects there to be a template in <code class="language-plaintext highlighter-rouge">lib/templates/#{page}.html.eex</code> which it evaluates, then passes in as the <code class="language-plaintext highlighter-rouge">inner_content</code> assign when expanding the <code class="language-plaintext highlighter-rouge">main</code> template.</p>

<h2 id="blog">Blog</h2>

<p>The blog is fed by markdown documents. I’m using <a href="https://hexdocs.pm/mdex/MDEx.html">MDex</a> (which uses a cool Rust NIF to be very fast) to parse them into an AST, then <a href="https://hexdocs.pm/yaml_elixir/YamlElixir.html">YamlElixir</a> to pull out the metadata.</p>

<p>Each blog post has a slug which generates the URL’s path, a date and a title, along with an abstract and fetching thumbnail to entice eager readers:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">date</span><span class="pi">:</span> <span class="s">2025-01-21</span>
<span class="na">tags</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">internet</span><span class="pi">,</span> <span class="nv">elixir</span><span class="pi">,</span> <span class="nv">mix</span><span class="pi">]</span>
<span class="na">slug</span><span class="pi">:</span> <span class="s">building-web-site</span>
<span class="na">title</span><span class="pi">:</span> <span class="s">How the Web Site is built</span>
<span class="na">abstract</span><span class="pi">:</span> <span class="s">A little breakdown on how this page is built and hosted</span>
<span class="na">thumbnail</span><span class="pi">:</span> <span class="s">/images/blog/03/fs-tree.jpg</span>
<span class="nn">---</span>

This homepage is half in-earnest technical project and half performance art. [...]
</code></pre></div></div>

<p>As a bonus, I’ve added an RSS feed, which I’ll talk about in a later post.</p>

<h2 id="photos">Photos</h2>

<p>My photos section is deliberately very low fidelity. There’s a few reasons: For one, I still hold reservations about just lashing high res photos I’ve taken online. Anything more than an “oh that’s nice” glance, like use as a wallpaper, or printing I’d like to know about (if you’d like a higher-resolution copy of any of my photos for this purpose, please email me! Let’s talk!).</p>

<p>Secondly, it would be nice to hopefully make them less useful to these collect-it-all AI crawlers. Perhaps I’ll try and find some way to poison or watermark them at some point.</p>

<p>Mostly though, I’m prioritising the early 2000s aesthetic (hence why I’ve turned off image smoothing in the main stylesheet).</p>

<p>In any case there’s a simple processing pipeline; images are fed in in dated folders, then I run <code class="language-plaintext highlighter-rouge">imagemagick</code> over all of them to resize them down to 256 x 256 pixels, convert them to JPEG and crucially, strip out any wayward EXIF metadata, like geo-location or Facebook’s image fingerprints (most of the images are from my Instagram archive).</p>

<p>These then get collected into per-month gallery pages.</p>

<h2 id="music">Music</h2>

<p>The only noteworthy addition to the music page is that I’m pulling in my top 100 artists from Last.fm via their developer API.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="n">top_artists</span><span class="p">()</span> <span class="k">do</span>
  <span class="n">get_artists</span><span class="p">()</span>
  <span class="o">|&gt;</span> <span class="n">handle_response</span><span class="p">()</span>
  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">sort_by</span><span class="p">(</span><span class="o">&amp;</span> <span class="nv">&amp;1</span><span class="o">.</span><span class="n">playcount</span><span class="p">)</span>
  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reverse</span><span class="p">()</span>
<span class="k">end</span>
</code></pre></div></div>

<h2 id="hosting">Hosting</h2>

<p>Finally, I wanted something really simple, in the sense of really easy to set up. CloudFlare pages’ free tier offers unlimited bandwidth, and the bare basics like HTTPS etc. I presume it’s also pushed out to their whole edge network, so it’s good to know folks from the US, to Europe to Oceania and beyond can load my buckets of content very fast.</p>

<p>Deployment is as trivial as running <code class="language-plaintext highlighter-rouge">npx wrangler pages deploy &lt;directory with assets&gt;</code>. They do clever content hashing so that you only upload new versions of files that have changed.</p>

<p>Hosting on CloudFlare is really interesting, and I may have a separate post about the things I’ve discovered. For one, they push content out to all of their 250+ edge locations, which is a far cry from my old homepage on BlackKnight. There’s also fine grained traffic filtering and monitoring which I’ve not delved into yet. For the moment, it’s free, unlimited and easy.</p>

<h2 id="wrapup">Wrapup</h2>

<p>And that’s about all! As I said it’s really about as simple as it gets, aside from maybe literally just using some bash scripts or something. Most of all though it’s extensible, and it’s <em>mine</em>.</p>

<p>Try making yours today!</p>]]></content><author><name></name></author><category term="internet" /><category term="elixir" /><category term="mix" /><summary type="html"><![CDATA[NOTE: I took the photos down because I broke the build script and couldn’t publish new articles, and couldn’t be bothered to fix it. Sorry!]]></summary></entry><entry><title type="html">Lo-fidelity Icelandic Choral Beats to Surf/Browse the web to</title><link href="/2025/01/18/background-music.html" rel="alternate" type="text/html" title="Lo-fidelity Icelandic Choral Beats to Surf/Browse the web to" /><published>2025-01-18T00:00:00+01:00</published><updated>2025-01-18T00:00:00+01:00</updated><id>/2025/01/18/background-music</id><content type="html" xml:base="/2025/01/18/background-music.html"><![CDATA[<p>In the in-between space and time of a long haul flight, I added some snazzy background music to the site, which you can listen to if you click on the headphones button under the nav bar.</p>

<p>This is a collage of <a href="https://www.youtube.com/watch?v=AQSNF6uvE_E">this fantastic time capsule of Icelandic Televison</a>, as well as some sampling of
<a href="https://en.wikipedia.org/wiki/Shen_Khar_Venakhi"><span title="Shen Khar Venakhi">შენ ხარ ვენახი</span></a>
(Thou Art a Vineyard)
<a href="https://www.youtube.com/watch?v=RH9zNz9L_VA">sung by the Rustavi Choir</a>.</p>

<p>I think it captures the mood very nicely.</p>]]></content><author><name></name></author><category term="ableton" /><category term="iceland" /><summary type="html"><![CDATA[In the in-between space and time of a long haul flight, I added some snazzy background music to the site, which you can listen to if you click on the headphones button under the nav bar.]]></summary></entry><entry><title type="html">Resorting Pages with Graphs</title><link href="/2025/01/15/graph-solution-advent.html" rel="alternate" type="text/html" title="Resorting Pages with Graphs" /><published>2025-01-15T00:00:00+01:00</published><updated>2025-01-15T00:00:00+01:00</updated><id>/2025/01/15/graph-solution-advent</id><content type="html" xml:base="/2025/01/15/graph-solution-advent.html"><![CDATA[<p>There’s a really neat graph-based solution to <a href="https://adventofcode.com/2024/day/5">Day 5 of 2024’s Advent of Code</a>.</p>

<p>I’m going to really heavily paraphrase, so you should read the problem spec if this is unclear. Essentially, we’re given a list of ‘page updates’, as pairs of page IDs, for example:</p>

<pre><code class="language-txt">47|53
97|13
97|61
</code></pre>

<p>Which means “page 47 must occur before 53, page 97 before 13, page 97 before 61”.</p>

<p>The next set of inputs are proposed sets of page orderings:</p>

<pre><code class="language-txt">97,61,53,29,13
75,29,13,97
</code></pre>

<p>So in line one, we place page ID 75, then page ID 47, … etc.</p>

<p>Part one of the problem is to <em>find the orderings which are valid</em>. So, given the set of rules we just ingested, check that the proposed orderings don’t break any of those rules. For example, the second one is invalid, since the last placement tells us to put page 97 after page 13, but our rules say 97 must always occur <em>before</em> 13.</p>

<p>In the second part, we’re asked to take all of the ones which are invalid, and then <em>put them in the right order</em> according to our rules.</p>

<hr />

<p>Naively, I started doing something like the following, take the rules as literal Prolog facts:</p>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ss">before</span><span class="p">(</span><span class="m">74</span><span class="p">,</span> <span class="m">53</span><span class="p">).</span>
<span class="ss">before</span><span class="p">(</span><span class="m">97</span><span class="p">,</span> <span class="m">13</span><span class="p">).</span>
<span class="ss">before</span><span class="p">(</span><span class="m">97</span><span class="p">,</span> <span class="m">61</span><span class="p">).</span>
</code></pre></div></div>

<p>Then checking the validity involves some sort of search. This encoding is incomplete though, since if we query <code class="language-plaintext highlighter-rouge">before(X, Y)</code> we only get solutions for if <code class="language-plaintext highlighter-rouge">X</code> and <code class="language-plaintext highlighter-rouge">Y</code> are <em>directly adjacent</em>.</p>

<p>We’re missing the fact that $a$ coming before $b$ is valid; the thin edge in the graphic above.</p>

<p>We can add another predicate:</p>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ss">before</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span> <span class="nv">Y</span><span class="p">)</span> <span class="p">:-</span>
    <span class="ss">before</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span> <span class="nv">A</span><span class="p">),</span>
    <span class="ss">before</span><span class="p">(</span><span class="nv">A</span><span class="p">,</span> <span class="nv">Y</span><span class="p">).</span>
</code></pre></div></div>

<p>This captures the idea that <code class="language-plaintext highlighter-rouge">X</code> appears before <code class="language-plaintext highlighter-rouge">Y</code> if we have some <code class="language-plaintext highlighter-rouge">A</code> which <code class="language-plaintext highlighter-rouge">X</code> is before, and that <code class="language-plaintext highlighter-rouge">A</code> itself is before <code class="language-plaintext highlighter-rouge">Y</code> (either as a direct match to one of the earlier facts, or as a recursive call to this rule.</p>

<p>Then we can validate an ordering by taking pairwise entries, and checking it’s correct by querying <code class="language-plaintext highlighter-rouge">before/2</code>.</p>

<p>This should work, but is still a bit too unidirectional. I’m not quite a good enough Prolog programmer to take this further (in the given day’s time to solve the challenge), so I switched gears to Elixir. As is usually the case, if we can find a better representation of our data, the mechanics of the solution should hopefully just fall out.</p>

<hr />

<p>We can rephrase the problem to be a bit more formal. Let’s define:</p>

<ul>
  <li>The set of page ids $I$, which in our case is just integers, but these can be arbitrary labels.</li>
  <li>The set of rules $R$, which are pairs of page IDs $(a, b)$, indicating that $a$ must appear before $b$.</li>
  <li>The set of orderings $O$, which is a proposed ordering of page IDs.</li>
</ul>

<p>Now here’s the graph bit, we can read $R$ to be a <em>set of edges</em> on a <em>directed graph</em> $G$, where $(a, b)$ indicates $a$ appearing before $b$. The graph $G$ now encodes quite some detail:</p>

<ul>
  <li>What page IDs are covered</li>
  <li>What page IDs are directly adjacent</li>
  <li>What page IDs <em>may</em> appear before another</li>
</ul>

<p>The last part is the cool part - $(a, b)$ is valid if $b$ is <em>reachable</em> from $a$, that is you can walk a path from <em>a</em> going through arbitrarily many nodes along the way, and end up at $b$ (what our rule <code class="language-plaintext highlighter-rouge">before/2</code>) encoded earlier.</p>

<p>Using <a href="#">LibGraph</a> for Elixir (and having turned the input into a graph as the “parse” step made the solution to part A stupidly succinct:</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Enum</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">updates</span><span class="p">,</span> <span class="o">&amp;</span><span class="no">Graph</span><span class="o">.</span><span class="n">is_subgraph?</span><span class="p">(</span><span class="nv">&amp;1</span><span class="p">,</span> <span class="n">ordering</span><span class="p">))</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">ordering</code> being a subgraph of the <code class="language-plaintext highlighter-rouge">updates</code> is expressing that all of the nodes in <code class="language-plaintext highlighter-rouge">ordering</code> have to appear in the same order as defined by <code class="language-plaintext highlighter-rouge">updates</code>, as explained above.</p>

<p>Another way to conceptualise graph <code class="language-plaintext highlighter-rouge">H</code> being a subgraph of graph <code class="language-plaintext highlighter-rouge">G</code>, is if we can <em>remove</em> intermediate nodes from <code class="language-plaintext highlighter-rouge">G</code> to eventually arrive at <code class="language-plaintext highlighter-rouge">H</code>:</p>

<p>The second part asks us to fix the out-of-order orderings, per the rules. This again ends up being really simple when modelled as graphs:</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">updates</span>
<span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reject</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Graph</span><span class="o">.</span><span class="n">is_subgraph?</span><span class="p">(</span><span class="nv">&amp;1</span><span class="p">,</span> <span class="n">ordering</span><span class="p">))</span>
<span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="k">fn</span> <span class="n">g</span> <span class="o">-&gt;</span>
    <span class="n">g</span>
    <span class="o">|&gt;</span> <span class="no">Graph</span><span class="o">.</span><span class="n">vertices</span><span class="p">()</span>
    <span class="o">|&gt;</span> <span class="n">then</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Graph</span><span class="o">.</span><span class="n">subgraph</span><span class="p">(</span><span class="n">ordering</span><span class="p">,</span> <span class="nv">&amp;1</span><span class="p">))</span>
    <span class="o">|&gt;</span> <span class="no">Graph</span><span class="o">.</span><span class="n">topsort</span><span class="p">()</span> 
<span class="k">end</span><span class="p">)</span>
</code></pre></div></div>

<p>We first filter out the ones that are correct, then perform a <em>topological sort</em> on the vertices of the updates which appear in the ordering. A topological sort is what it sounds like, rather than sorting e.g. by some comparator, we sort them by the order in which they show up in the graph. Essentially, walk it from the start to the end, listing out the edges as you see them.</p>

<p>And that’s about it! I’ve probably not explained this too well, but I thought it was really neat. The full code for the solution is on <a href="https://github.com/shawa/advent-of-code-2024">my GitHub repo</a> which contains partial solutions for the rest of 2024. Onward to more graphs.</p>]]></content><author><name></name></author><category term="advent-of-code" /><category term="graph-theory" /><category term="internet" /><summary type="html"><![CDATA[There’s a really neat graph-based solution to Day 5 of 2024’s Advent of Code.]]></summary></entry><entry><title type="html">A Breath of Air</title><link href="/2025/01/01/a-breath-of-air.html" rel="alternate" type="text/html" title="A Breath of Air" /><published>2025-01-01T00:00:00+01:00</published><updated>2025-01-01T00:00:00+01:00</updated><id>/2025/01/01/a-breath-of-air</id><content type="html" xml:base="/2025/01/01/a-breath-of-air.html"><![CDATA[<p>I think it worse looks great.</p>

<p>I stumbled across <a href="https://cameronsworld.net">https://cameronsworld.net</a> the other day. Go and visit it and come back here first.</p>

<p>It’s a fantastic full on, in-your-face torrent of nostalgia for the early web. The kind of web you’d <em>surf</em>. People had little gardens of web sites they’d tend to.</p>

<p><span style="color: red; text-shadow: 3px 3px 0px gray; text-align: center; font-size: 2em; font-style: italic; font-family: papyrus;">Choose Joy!</span></p>

<p>My first exposure to programming proper was my first ever ‘homepage’, after we got PHP set up on the OS X Leopard webserver.</p>

<p>I spend hours, days, weeks fiddling with absolute positioned divs, the magic of my <code class="language-plaintext highlighter-rouge">navbar.inc.php</code> propagating automatically across <em>all</em> of my pages.</p>

<p>…and that led me to</p>

<ul>
  <li>Learning about AppleScript and making little OS X ‘applications’, then Learn Python the Hard Way, then Higher Or</li>
  <li>Being gifted <a href="https://link.springer.com/book/10.1007/978-1-4302-1633-9">Python 3 for Absolute Beginners</a>, then going on to read https://learnpythonthehardway.org/</li>
  <li>Then coming across <a href="https://hop.perl.plover.com/">Higher Order Perl</a> and loving Perl, to then learn about <a href="https://docs.raku.org/language/optut">Perl 6</a>, with <span alt="what the hell is post circumfix"><code class="language-plaintext highlighter-rouge">postcircumfix</code></span> operators:</li>
</ul>

<pre><code class="language-raku">sub postcircumfix:&lt;⸨ ⸩&gt;( Positional $a, Whatever ) {
    say $a[0], '…', $a[*-1]
}

[1,2,3,4]⸨*⸩;      # OUTPUT: «1…4␤»

constant term:&lt;♥&gt; = "♥"; # We don't want to quote "love", do we?
sub circumfix:&lt;α ω&gt;( $a ) {
    say „$a is the beginning and the end.“
};

α♥ω;               # OUTPUT: «♥ is the beginning and the end.␤»
</code></pre>

<blockquote>
  <p>Seriously, wtf</p>
</blockquote>

<ul>
  <li>Then <a href="https://learnyouahaskell.github.io/introduction.html">Learn You as Haskell</a>, followed by <a href="https://learnyousomeerlang.com/">Learn You Some Erlang</a></li>
</ul>

<p>So, the ginormous tech platforms <em>do</em> control basically all the ‘the internet’.</p>

<p>And the platforms <em>are</em> run by billionaires, who are most interested in <a href="https://duckduckgo.com/?t=h_&amp;q=high+arousal+emotions+online+content">high arousal emotions</a> and people being mad at each other and all that stuff, driving engagement etc.</p>

<p>But the thing about the internet is that it’s not finite, or zero sum, you can just make more of it.</p>

<p>Just buy a domain, stick a few pages up on a webserver and you’ve got your little slice of gif-and-banner peace. Just like, go somewhere else instead of the big platforms. Email your friend.</p>

<p>Write a blog no one will read!</p>]]></content><author><name></name></author><category term="geocities" /><category term="internet" /><summary type="html"><![CDATA[I think it worse looks great.]]></summary></entry></feed>